From 8cf0427984a88b0b3ddfb2061e5be721afffe82e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 17:19:56 -0700 Subject: Move Core into wix --- .editorconfig | 37 - README.md | 3 - WixToolset.Core.sln | 156 - WixToolset.Core.v3.ncrunchsolution | 6 - appveyor.cmd | 20 - appveyor.yml | 44 - nuget.config | 13 - src/.editorconfig | 37 + src/Custom.Build.props | 6 - src/Directory.Build.props | 27 - src/Directory.Build.targets | 51 - src/Directory.csproj.props | 13 - src/Directory.csproj.targets | 26 - src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | 27 - src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 650 -- .../Bind/ExtensionSearchFacade.cs | 24 - .../Bind/GenerateManifestDataFromIRCommand.cs | 237 - .../Bind/LegacySearchFacade.cs | 185 - .../Bind/ProcessBundleSoftwareTagsCommand.cs | 133 - .../Bind/ProcessDependencyProvidersCommand.cs | 147 - .../Bind/ResolveDownloadUrlsCommand.cs | 128 - .../Bind/SetVariableSearchFacade.cs | 48 - src/WixToolset.Core.Burn/BundleBackend.cs | 77 - .../AutomaticallySlipstreamPatchesCommand.cs | 117 - .../Bundles/BundleHashAlgorithm.cs | 30 - src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 385 - src/WixToolset.Core.Burn/Bundles/BurnReader.cs | 212 - src/WixToolset.Core.Burn/Bundles/BurnWriter.cs | 245 - ...CreateBootstrapperApplicationManifestCommand.cs | 290 - .../Bundles/CreateBundleExeCommand.cs | 325 - .../CreateBundleExtensionManifestCommand.cs | 99 - .../Bundles/CreateBurnManifestCommand.cs | 700 -- .../Bundles/CreateContainerCommand.cs | 70 - .../Bundles/CreateNonUXContainers.cs | 151 - .../Bundles/DetectPayloadCollisionsCommand.cs | 137 - .../Bundles/GetPackageFacadesCommand.cs | 181 - .../OrderPackagesAndRollbackBoundariesCommand.cs | 171 - .../Bundles/OrderSearchesCommand.cs | 367 - src/WixToolset.Core.Burn/Bundles/PackageFacade.cs | 25 - .../Bundles/ProcessExePackageCommand.cs | 39 - .../Bundles/ProcessMsiPackageCommand.cs | 558 -- .../Bundles/ProcessMspPackageCommand.cs | 183 - .../Bundles/ProcessMsuPackageCommand.cs | 37 - .../Bundles/ProcessPayloadsCommand.cs | 108 - src/WixToolset.Core.Burn/BurnBackendErrors.cs | 72 - src/WixToolset.Core.Burn/BurnBackendFactory.cs | 30 - src/WixToolset.Core.Burn/BurnBackendWarnings.cs | 36 - src/WixToolset.Core.Burn/BurnExtensionFactory.cs | 22 - .../ExtensibilityServices/BurnBackendHelper.cs | 214 - .../ExtensibilityServices/PayloadHarvester.cs | 68 - .../IInternalBurnBackendHelper.cs | 14 - src/WixToolset.Core.Burn/ISearchFacade.cs | 15 - .../Inscribe/InscribeBundleCommand.cs | 54 - .../Inscribe/InscribeBundleEngineCommand.cs | 63 - .../Interfaces/IPayloadHarvester.cs | 23 - src/WixToolset.Core.Burn/RowIndexedList.cs | 299 - .../WixToolset.Core.Burn.csproj | 39 - .../WixToolsetCoreServiceProviderExtensions.cs | 45 - .../CachedExtension.cs | 20 - .../ExtensionCacheManager.cs | 248 - .../ExtensionCacheManagerCommand.cs | 181 - .../ExtensionCacheManagerExtensionCommandLine.cs | 41 - .../ExtensionCacheManagerExtensionFactory.cs | 30 - .../WixToolset.Core.ExtensionCache.csproj | 29 - .../WixToolsetCoreServiceProviderExtensions.cs | 36 - src/WixToolset.Core.TestPackage/BundleExtractor.cs | 139 - .../ExtractBAContainerResult.cs | 116 - .../TestMessageListener.cs | 55 - src/WixToolset.Core.TestPackage/WixRunner.cs | 88 - src/WixToolset.Core.TestPackage/WixRunnerResult.cs | 62 - .../WixToolset.Core.TestPackage.csproj | 34 - .../XmlNodeExtensions.cs | 90 - .../Bind/AddBackSuppressedSequenceTablesCommand.cs | 52 - .../Bind/AddCreateFoldersCommand.cs | 38 - .../Bind/AddRequiredStandardDirectories.cs | 95 - .../Bind/AssemblyName.cs | 60 - .../Bind/AssemblyNameReader.cs | 214 - .../Bind/AssignMediaCommand.cs | 302 - .../Bind/AttachPatchTransformsCommand.cs | 1305 --- .../Bind/BindDatabaseCommand.cs | 646 -- .../Bind/BindSummaryInfoCommand.cs | 211 - .../Bind/BindTransformCommand.cs | 445 - .../Bind/CabinetBuilder.cs | 171 - .../Bind/CabinetResolver.cs | 132 - .../Bind/CabinetWorkItem.cs | 68 - .../Bind/CreateCabinetsCommand.cs | 455 -- .../Bind/CreateDeltaPatchesCommand.cs | 81 - .../Bind/CreateIdtFileCommand.cs | 250 - .../Bind/CreateInstanceTransformsCommand.cs | 260 - .../Bind/CreatePatchTransformsCommand.cs | 93 - .../Bind/CreateSpecialPropertiesCommand.cs | 83 - .../CreateWindowsInstallerDataFromIRCommand.cs | 1621 ---- .../Bind/ExtractMergeModuleFilesCommand.cs | 221 - .../Bind/FileSystemManager.cs | 77 - .../Bind/FinalizeComponentGuids.cs | 262 - .../Bind/GenerateDatabaseCommand.cs | 408 - .../Bind/GenerateTransformCommand.cs | 582 -- .../Bind/GetFileFacadesCommand.cs | 157 - .../Bind/GetFileFacadesFromTransforms.cs | 174 - .../Bind/LoadTableDefinitionsCommand.cs | 215 - .../Bind/MergeModulesCommand.cs | 331 - .../Bind/ModularizeCommand.cs | 236 - .../Bind/OptimizeFileFacadesOrderCommand.cs | 119 - .../Bind/PatchTransform.cs | 19 - .../Bind/ProcessDependencyReferencesCommand.cs | 114 - .../Bind/ProcessPackageSoftwareTagsCommand.cs | 131 - .../Bind/ProcessPropertiesCommand.cs | 101 - .../Bind/ProcessUncompressedFilesCommand.cs | 125 - .../Bind/SequenceActionsCommand.cs | 714 -- .../Bind/UpdateFileFacadesCommand.cs | 365 - .../Bind/UpdateFromTextFilesCommand.cs | 77 - .../Bind/UpdateMediaSequencesCommand.cs | 109 - .../Bind/UpdateTransformsWithFileFacades.cs | 451 -- .../Bind/ValidateDatabaseCommand.cs | 187 - .../Decompile/DecompileMsiOrMsmCommand.cs | 96 - .../Decompile/Decompiler.cs | 7596 ----------------- .../Decompile/Names.cs | 160 - src/WixToolset.Core.WindowsInstaller/Differ.cs | 610 -- .../WindowsInstallerBackendHelper.cs | 121 - .../Inscribe/InscribeMsiPackageCommand.cs | 272 - src/WixToolset.Core.WindowsInstaller/Melter.cs | 399 - src/WixToolset.Core.WindowsInstaller/MelterCore.cs | 32 - src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 85 - src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 76 - src/WixToolset.Core.WindowsInstaller/MspBackend.cs | 162 - src/WixToolset.Core.WindowsInstaller/MstBackend.cs | 44 - .../RowDictionary.cs | 71 - .../Unbind/ExtractCabinetsCommand.cs | 147 - .../Unbind/UnbindDatabaseCommand.cs | 789 -- .../Unbind/UnbindMsiOrMsmCommand.cs | 55 - .../Unbind/UnbindTranformCommand.cs | 309 - .../WindowsInstallerBackendErrors.cs | 24 - .../WindowsInstallerBackendFactory.cs | 51 - .../WindowsInstallerBackendWarnings.cs | 24 - .../WindowsInstallerExtensionFactory.cs | 22 - .../WixToolset.Core.WindowsInstaller.csproj | 30 - .../WixToolsetCoreServiceProviderExtensions.cs | 42 - src/WixToolset.Core/Bind/DelayedField.cs | 35 - src/WixToolset.Core/Bind/ExpectedExtractFile.cs | 16 - src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 92 - .../Bind/ExtractEmbeddedFilesCommand.cs | 53 - src/WixToolset.Core/Bind/FileResolver.cs | 207 - .../Bind/ResolveDelayedFieldsCommand.cs | 164 - src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 276 - src/WixToolset.Core/Bind/TransferFilesCommand.cs | 196 - src/WixToolset.Core/BindContext.cs | 65 - src/WixToolset.Core/BindFileWithPath.cs | 22 - src/WixToolset.Core/BindPath.cs | 20 - src/WixToolset.Core/BindResult.cs | 48 - src/WixToolset.Core/Binder.cs | 96 - src/WixToolset.Core/CommandLine/BuildCommand.cs | 912 --- src/WixToolset.Core/CommandLine/CommandLine.cs | 199 - .../CommandLine/CommandLineArguments.cs | 207 - .../CommandLine/CommandLineContext.cs | 22 - .../CommandLine/CommandLineParser.cs | 270 - src/WixToolset.Core/CommandLine/CompileCommand.cs | 94 - .../CommandLine/DecompileCommand.cs | 256 - src/WixToolset.Core/CommandLine/HelpCommand.cs | 66 - src/WixToolset.Core/CommandLine/VersionCommand.cs | 26 - src/WixToolset.Core/Common.cs | 832 -- src/WixToolset.Core/Compile/CompilerPayload.cs | 291 - src/WixToolset.Core/CompileContext.cs | 34 - src/WixToolset.Core/Compiler.cs | 8514 -------------------- src/WixToolset.Core/CompilerCore.cs | 1166 --- src/WixToolset.Core/CompilerErrors.cs | 43 - src/WixToolset.Core/CompilerWarnings.cs | 65 - src/WixToolset.Core/Compiler_Bundle.cs | 3266 -------- src/WixToolset.Core/Compiler_Dependency.cs | 384 - src/WixToolset.Core/Compiler_EmbeddedUI.cs | 417 - src/WixToolset.Core/Compiler_Module.cs | 662 -- src/WixToolset.Core/Compiler_Package.cs | 4996 ------------ src/WixToolset.Core/Compiler_Patch.cs | 657 -- src/WixToolset.Core/Compiler_PatchCreation.cs | 1265 --- src/WixToolset.Core/Compiler_Tag.cs | 315 - src/WixToolset.Core/Compiler_UI.cs | 1808 ----- src/WixToolset.Core/ComponentKeyPath.cs | 25 - src/WixToolset.Core/DecompileContext.cs | 49 - src/WixToolset.Core/DecompileResult.cs | 18 - src/WixToolset.Core/Decompiler.cs | 68 - .../ExtensibilityServices/BackendHelper.cs | 176 - .../ExtensibilityServices/ExtensionManager.cs | 233 - .../ExtensibilityServices/FileFacade.cs | 172 - .../ExtensibilityServices/FileTransfer.cs | 20 - .../ExtensibilityServices/Messaging.cs | 99 - .../ExtensibilityServices/ParseHelper.cs | 863 -- .../ExtensibilityServices/PathResolver.cs | 118 - .../ExtensibilityServices/PreprocessHelper.cs | 499 -- .../ExtensibilityServices/ResolvedDirectory.cs | 15 - .../SymbolDefinitionCreator.cs | 70 - .../ExtensibilityServices/TrackedFile.cs | 26 - src/WixToolset.Core/ExtensibilityServices/Uuid.cs | 81 - .../ExtensibilityServices/WixBranding.cs | 124 - src/WixToolset.Core/IBinder.cs | 12 - src/WixToolset.Core/ICompiler.cs | 13 - src/WixToolset.Core/IDecompiler.cs | 12 - src/WixToolset.Core/ILayoutCreator.cs | 12 - src/WixToolset.Core/ILibrarian.cs | 13 - src/WixToolset.Core/ILinker.cs | 13 - src/WixToolset.Core/ILocalizationParser.cs | 27 - src/WixToolset.Core/IPreprocessor.cs | 15 - src/WixToolset.Core/IResolver.cs | 19 - src/WixToolset.Core/IUnbinder.cs | 12 - src/WixToolset.Core/IncludedFile.cs | 14 - src/WixToolset.Core/IncribeContext.cs | 26 - src/WixToolset.Core/LayoutContext.cs | 40 - src/WixToolset.Core/LayoutCreator.cs | 223 - src/WixToolset.Core/Librarian.cs | 135 - src/WixToolset.Core/LibraryContext.cs | 38 - .../Link/CollateLocalizationsCommand.cs | 71 - src/WixToolset.Core/Link/ConnectToFeature.cs | 59 - .../Link/ConnectToFeatureCollection.cs | 92 - src/WixToolset.Core/Link/ConnectToModule.cs | 54 - .../Link/ConnectToModuleCollection.cs | 92 - .../Link/FindEntrySectionAndLoadSymbolsCommand.cs | 119 - .../Link/FlattenAndProcessBundleTablesCommand.cs | 194 - .../Link/IntermediateSymbolExtensions.cs | 26 - .../Link/ReportConflictingSymbolsCommand.cs | 54 - .../Link/ResolveReferencesCommand.cs | 183 - src/WixToolset.Core/Link/SymbolWithSection.cs | 92 - .../Link/WixComplexReferenceSymbolExtensions.cs | 75 - src/WixToolset.Core/Link/WixGroupingOrdering.cs | 683 -- src/WixToolset.Core/LinkContext.cs | 33 - src/WixToolset.Core/Linker.cs | 942 --- src/WixToolset.Core/LinkerErrors.cs | 48 - src/WixToolset.Core/LinkerWarnings.cs | 36 - src/WixToolset.Core/LocalizationParser.cs | 326 - src/WixToolset.Core/ParsedWixVariable.cs | 19 - src/WixToolset.Core/Preprocess/IfContext.cs | 74 - .../Preprocess/IfDefEventHandler.cs | 28 - src/WixToolset.Core/Preprocess/IfState.cs | 22 - .../Preprocess/IncludedFileEventHandler.cs | 43 - .../Preprocess/PreprocessorOperation.cs | 19 - .../Preprocess/ProcessedStreamEventHandler.cs | 43 - .../Preprocess/ResolvedVariableEventHandler.cs | 25 - src/WixToolset.Core/PreprocessContext.cs | 35 - src/WixToolset.Core/PreprocessResult.cs | 15 - src/WixToolset.Core/Preprocessor.cs | 1520 ---- src/WixToolset.Core/Properties/AssemblyInfo.cs | 9 - src/WixToolset.Core/ResolveContext.cs | 42 - src/WixToolset.Core/ResolveFileResult.cs | 14 - src/WixToolset.Core/ResolveResult.cs | 23 - src/WixToolset.Core/ResolvedCabinet.cs | 22 - src/WixToolset.Core/Resolver.cs | 304 - src/WixToolset.Core/SourceFile.cs | 17 - src/WixToolset.Core/UnbindContext.cs | 29 - src/WixToolset.Core/Unbinder.cs | 99 - src/WixToolset.Core/VariableResolution.cs | 29 - src/WixToolset.Core/VariableResolver.cs | 197 - src/WixToolset.Core/WixToolset.Core.csproj | 47 - .../WixToolset.Core.v3.ncrunchproject | 7 - src/WixToolset.Core/WixToolsetServiceProvider.cs | 117 - .../WixToolsetServiceProviderFactory.cs | 21 - .../CompileCoreTestExtensionWixlib.csproj | 32 - src/test/CompileCoreTestExtensionWixlib/Program.cs | 37 - src/test/Example.Extension/Data/example.txt | 1 - src/test/Example.Extension/Data/example.wxs | 15 - .../Example.Extension/Example.Extension.csproj | 24 - .../Example.Extension/ExampleCompilerExtension.cs | 195 - src/test/Example.Extension/ExampleExtensionData.cs | 23 - .../Example.Extension/ExampleExtensionFactory.cs | 54 - .../ExamplePreprocessorExtensionAndCommandLine.cs | 57 - src/test/Example.Extension/ExampleRow.cs | 32 - src/test/Example.Extension/ExampleSearchSymbol.cs | 30 - src/test/Example.Extension/ExampleSymbol.cs | 30 - .../Example.Extension/ExampleSymbolDefinitions.cs | 67 - .../Example.Extension/ExampleTableDefinitions.cs | 34 - .../ExampleWindowsInstallerBackendExtension.cs | 33 - .../WixToolsetTest.Core.Burn/BurnReaderFixture.cs | 44 - .../WixToolsetTest.Core.Burn.csproj | 28 - .../ApprovedExeFixture.cs | 64 - .../BadInputFixture.cs | 148 - .../BindVariablesFixture.cs | 96 - .../BootstrapperApplicationFixture.cs | 46 - .../BundleExtractionFixture.cs | 58 - .../BundleFixture.cs | 478 -- .../BundleManifestFixture.cs | 365 - .../WixToolsetTest.CoreIntegration/CabFixture.cs | 107 - .../ComponentFixture.cs | 45 - .../ContainerFixture.cs | 385 - .../CopyFileFixture.cs | 48 - .../CustomActionFixture.cs | 169 - .../CustomTableFixture.cs | 234 - .../DecompileFixture.cs | 86 - .../DependencyExtensionFixture.cs | 180 - .../DirectoryFixture.cs | 271 - .../ExePackageFixture.cs | 52 - .../ExtensionFixture.cs | 153 - .../LanguageFixture.cs | 174 - .../LinkerFixture.cs | 174 - .../WixToolsetTest.CoreIntegration/MediaFixture.cs | 62 - .../ModuleFixture.cs | 113 - .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 838 -- .../MsiQueryFixture.cs | 1040 --- .../MsiTransactionFixture.cs | 131 - .../MsuPackageFixture.cs | 36 - .../PackagePayloadFixture.cs | 211 - .../WixToolsetTest.CoreIntegration/ParseFixture.cs | 36 - .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 279 - .../PayloadFixture.cs | 212 - .../PreprocessorFixture.cs | 181 - .../RegistryFixture.cs | 173 - .../RollbackBoundaryFixture.cs | 41 - .../ShortcutFixture.cs | 78 - .../SoftwareTagFixture.cs | 100 - .../TestData/.Data/burn.exe | Bin 463360 -> 0 bytes .../TestData/AppId/Advertised.wxs | 11 - .../TestData/AppSearch/ComponentSearch.wxs | 12 - .../DecompiledNestedDirSearchUnderRegSearch.wxs | 42 - .../TestData/AppSearch/DirectorySearch.wxs | 12 - .../TestData/AppSearch/FileSearch.wxs | 14 - .../AppSearch/NestedDirSearchUnderRegSearch.msi | Bin 33045 -> 0 bytes .../TestData/AppSearch/RegistrySearch.wxs | 12 - .../TestData/AppSearch/RegistrySearch64.wxs | 12 - .../TestData/Assembly/Package.en-us.wxl | 11 - .../TestData/Assembly/Package.wxs | 17 - .../TestData/Assembly/PackageComponents.wxs | 10 - .../TestData/Assembly/Win32Assembly.wxs | 13 - .../TestData/Assembly/data/candle.exe | Bin 28672 -> 0 bytes .../TestData/Assembly/data/test.manifest | 76 - .../TestData/BadEnsureTable/BadEnsureTable.wxs | 11 - .../TestData/BadIf/Package.en-us.wxl | 11 - .../TestData/BadIf/Package.wxs | 24 - .../TestData/BadIf/PackageComponents.wxs | 12 - .../TestData/BadIf/data/test.txt | 1 - .../TestData/BadInput/BundleVariable.wxs | 6 - .../TestData/BadInput/DuplicateCacheIds.wxs | 12 - .../TestData/BadInput/DuplicatePayloadNames.wxs | 31 - .../BadInput/HiddenPersistedBundleVariable.wxs | 6 - .../TestData/BadInput/InvalidIds.wxs | 8 - .../TestData/BadInput/RegistryKey.wxs | 13 - .../TestData/BadInput/UnscheduledPackage.wxs | 16 - .../BadInput/UnscheduledRollbackBoundary.wxs | 16 - .../TestData/BindVariables/DefaultedVariable.wxs | 6 - .../TestData/BindVariables/data/test.txt | 1 - .../BootstrapperApplication/DpiAwareness.wxs | 7 - .../CacheIdFromPackageDescription.wxs | 8 - .../BundleCustomTable/BundleCustomTable.wxs | 53 - .../TestData/BundleExtension/BundleExtension.wxs | 6 - .../BundleExtension/BundleExtensionSearches.wxs | 8 - .../BundleExtension/BundleWithSearches.wxs | 11 - .../BundleExtension/SimpleBundleExtension.wxs | 10 - .../TestData/BundleTag/BundleWithTag.wxs | 15 - .../TestData/BundleTag/fakeba.dll | 1 - .../TestData/BundleWithApprovedExe/Bundle.wxs | 5 - .../TestData/BundleWithApprovedExe/Bundle64.wxs | 5 - .../BundleWithDetachedContainer/Bundle.wxs | 10 - .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 10 - .../MinimalPackageGroup.wxs | 8 - .../TestData/Class/DecompiledOldClassTableDef.wxs | 22 - .../TestData/Class/IconIndex0.wxs | 11 - .../TestData/Class/OldClassTableDef.msi | Bin 36864 -> 0 bytes .../ComplexExampleExtension/OtherComponents.wxs | 12 - .../ComplexExampleExtension/Package.en-us.wxl | 11 - .../TestData/ComplexExampleExtension/Package.wxs | 22 - .../ComplexExampleExtension/PackageComponents.wxs | 12 - .../ComplexExampleExtension/data/example.txt | 1 - .../ComplexExampleExtension/data/other.txt | 1 - .../TestData/Component/GuidCollision.wxs | 14 - .../TestData/Components/Package.en-us.wxl | 11 - .../TestData/Components/Package.wxs | 17 - .../TestData/Components/PackageComponents.wxs | 10 - .../TestData/Components/data/test.txt | 1 - .../Container/HarvestIntoDetachedContainer.wxs | 15 - .../Container/MultipleAttachedContainers.wxs | 15 - .../TestData/CopyFile/CopyFile.wxs | 17 - .../TestData/CustomAction/CustomActionCycle.wxs | 18 - .../CustomAction/CustomActionCycleWithTail.wxs | 20 - .../TestData/CustomAction/SimpleCustomAction.wxs | 14 - .../CustomAction/UnscheduledCustomAction.wxs | 32 - .../CustomPackageDescription.wxs | 12 - .../TestData/CustomTable/CustomTable-Expected.wxs | 29 - .../TestData/CustomTable/CustomTable.wxs | 34 - .../TestData/CustomTable/CustomTableWithFile.wxs | 22 - .../CustomTable/LocalizedCustomTable.en-us.wxl | 7 - .../TestData/CustomTable/LocalizedCustomTable.wxs | 21 - .../TestData/CustomTable/data/file1.txt | 1 - .../TestData/CustomTable/data/file2.txt | 1 - .../TestData/CustomTable/data/test.txt | 1 - .../TestData/DecompileNullComponent/Expected.wxs | 16 - .../TestData/DecompileNullComponent/example.cab | Bin 137 -> 0 bytes .../TestData/DecompileNullComponent/example.msi | Bin 32768 -> 0 bytes .../DecompileSingleFileCompressed/Expected.wxs | 16 - .../DecompileSingleFileCompressed/example.cab | Bin 137 -> 0 bytes .../DecompileSingleFileCompressed/example.msi | Bin 32768 -> 0 bytes .../DecompileSingleFileCompressed64/Expected.wxs | 16 - .../DecompileSingleFileCompressed64/example.cab | Bin 137 -> 0 bytes .../DecompileSingleFileCompressed64/example.msi | Bin 32768 -> 0 bytes .../DecompileTargetDirMergeModule/Expected.wxs | 18 - .../DecompileTargetDirMergeModule/MergeModule1.msm | Bin 32768 -> 0 bytes .../TestData/DefaultDir/DefaultDir.wxs | 26 - .../Dependency/CustomProviderKeyBundle.wxs | 10 - .../Dependency/ExePackageProvidesBundle.wxs | 10 - .../TestData/Dependency/UsingProvidesBundle.wxs | 8 - .../PackageComponents.wxs | 36 - .../TestData/Directory/DefaultName.wxs | 13 - .../Directory/DuplicateTargetSourceName.wxs | 11 - .../TestData/Directory/Empty.wxs | 6 - .../TestData/Directory/Nested.wxs | 11 - .../TestData/DuplicateDir/DuplicateDir.wxs | 25 - .../TestData/EnsureTable/EnsureTable.wxs | 10 - .../TestData/Environment/Environment.wxs | 14 - .../TestData/ErrorsInUI/Package.en-us.wxl | 9 - .../TestData/ErrorsInUI/Package.wxs | 20 - .../TestData/ErrorsInUI/PackageComponents.wxs | 14 - .../TestData/ErrorsInUI/data/test.txt | 1 - .../TestData/ExampleExtension/Package.en-us.wxl | 11 - .../TestData/ExampleExtension/Package.wxs | 21 - .../ExampleExtension/PackageComponents.wxs | 12 - .../TestData/ExampleExtension/data/example.txt | 1 - .../TestData/ExePackage/MissingDetectCondition.wxs | 9 - .../TestData/ExePackage/RequireDetectCondition.wxs | 11 - .../TestData/FeatureGroup/FeatureGroup.wxs | 14 - .../TestData/Font/FontTitle.wxs | 10 - .../TestData/Font/TrueType.wxs | 10 - .../TestData/ForEach/Package.en-us.wxl | 11 - .../TestData/ForEach/Package.wxs | 19 - .../TestData/ForEach/PackageComponents.wxs | 12 - .../TestData/ForEach/data/test.txt | 1 - .../TestData/Icon/SampleIcon.wxs | 6 - .../TestData/IncludePath/Package.en-us.wxl | 11 - .../TestData/IncludePath/Package.wxs | 18 - .../TestData/IncludePath/PackageComponents.wxs | 8 - .../TestData/IncludePath/data/DontDoThis.wxi | 6 - .../TestData/IncludePath/data/Package.wxi | 4 - .../TestData/IncludePath/data/test.txt | 1 - .../TestData/InstanceTransform/Package.en-us.wxl | 11 - .../TestData/InstanceTransform/Package.wxs | 23 - .../InstanceTransform/PackageComponents.wxs | 10 - .../TestData/InstanceTransform/data/test.txt | 1 - .../TestData/Language/Package.en-us.wxl | 7 - .../TestData/Language/Package.ja-jp.wxl | 7 - .../TestData/Language/Package.wxl | 7 - .../TestData/Language/Package.wxs | 18 - .../Language/PackageWithEnSummaryInfo.ja-jp.wxl | 7 - .../TestData/Language/data/test.txt | 1 - .../TestData/LockPermissions/EmptyPermissions.wxs | 13 - .../TestData/ManualUpgrade/Package.en-us.wxl | 11 - .../TestData/ManualUpgrade/Package.wxs | 24 - .../TestData/ManualUpgrade/PackageComponents.wxs | 10 - .../TestData/ManualUpgrade/data/test.txt | 1 - .../TestData/Media/MultiMedia.wxs | 28 - .../TestData/Media/data/a1.txt | 1 - .../TestData/Media/data/a2.txt | 1 - .../TestData/Media/data/b1.txt | 1 - .../TestData/Media/data/b2.txt | 1 - .../TestData/MsiTransaction/FirstX64.wxs | 8 - .../TestData/MsiTransaction/FirstX86.wxs | 8 - .../TestData/MsiTransaction/SecondX64.wxs | 8 - .../TestData/MsiTransaction/SecondX86.wxs | 8 - .../TestData/MsiTransaction/X64AfterX86Bundle.wxs | 12 - .../TestData/MsiTransaction/X86AfterX64Bundle.wxs | 12 - .../TestData/MsuPackage/Bundle.wxs | 11 - .../TestData/MsuPackage/data/fakeba.dll | 1 - .../TestData/MsuPackage/data/test.msu | 1 - .../TestData/MultiFileCompressed/Package.en-us.wxl | 11 - .../TestData/MultiFileCompressed/Package.wxs | 26 - .../MultiFileCompressed/PackageComponents.wxs | 13 - .../TestData/MultiFileCompressed/data/test.txt | 1 - .../TestData/OverridableActions/Package.en-us.wxl | 11 - .../TestData/OverridableActions/Package.wxs | 48 - .../OverridableActions/PackageComponents.wxs | 10 - .../TestData/OverridableActions/data/test.txt | 1 - .../PackagePayload/MissingSourceFileAndHash.wxs | 10 - .../PackagePayload/MissingSourceFileAndName.wxs | 10 - .../PackagePayloadInPayloadGroup.wxs | 15 - .../TestData/PackagePayload/SpecifiedHash.wxs | 10 - .../SpecifiedHashAndMissingDownloadUrl.wxs | 10 - .../PackagePayload/SpecifiedSourceFileAndHash.wxs | 10 - .../WrongPackagePayloadInPayloadGroup.wxs | 15 - .../TestData/PatchFamilyFilter/.data/Av1.0.0.txt | 1 - .../TestData/PatchFamilyFilter/.data/Av1.0.1.txt | 1 - .../TestData/PatchFamilyFilter/.data/Bv1.0.0.txt | 1 - .../TestData/PatchFamilyFilter/.data/Bv1.0.1.txt | 1 - .../TestData/PatchFamilyFilter/Package.wxs | 28 - .../TestData/PatchFamilyFilter/Patch.wxs | 16 - .../TestData/PatchFromWixlib/Package.wxs | 30 - .../TestData/PatchFromWixlib/Patch.wxs | 16 - .../TestData/PatchNoFileChanges/.data/A.txt | 1 - .../TestData/PatchNoFileChanges/Package.wxs | 27 - .../TestData/PatchNoFileChanges/Patch.wxs | 16 - .../TestData/PatchNonSpecific/BundleA/Bundle.wxs | 7 - .../TestData/PatchNonSpecific/BundleB/Bundle.wxs | 8 - .../TestData/PatchNonSpecific/BundleC/Bundle.wxs | 9 - .../TestData/PatchNonSpecific/PackageA/Package.wxs | 44 - .../TestData/PatchNonSpecific/PatchA/Patch.wxs | 12 - .../TestData/PatchNonSpecific/PatchB/Patch.wxs | 14 - .../TestData/PatchNonSpecific/PatchC/Patch.wxs | 14 - .../TestData/PatchSingle/.data/Av1.0.0.txt | 1 - .../TestData/PatchSingle/.data/Av1.0.1.txt | 1 - .../TestData/PatchSingle/.data/Bv1.0.0.txt | 1 - .../TestData/PatchSingle/.data/Bv1.0.1.txt | 1 - .../TestData/PatchSingle/BundleA/Bundle.wxs | 10 - .../TestData/PatchSingle/Package.wxs | 27 - .../TestData/PatchSingle/Patch.wxs | 16 - .../TestData/Payload/AbsoluteName.wxs | 9 - .../TestData/Payload/CanonicalizeName.wxs | 7 - .../Payload/DownloadUrlPlaceholdersBundle.wxs | 30 - .../Payload/SharedBAAndPackagePayloadBundle.wxs | 16 - .../TestData/Payload/ValidName.wxs | 7 - .../TestData/Preprocessor/EnvParens.wxs | 4 - .../TestData/ProductTag/Package.en-us.wxl | 11 - .../TestData/ProductTag/PackageComponents.wxs | 10 - .../TestData/ProductTag/PackageWithTag.wxs | 18 - .../TestData/ProductTag/example.txt | 1 - .../MinimalComponentGroup.wxs | 10 - .../ProductWithComponentGroupRef/Product.wxs | 19 - .../TestData/ProgId/NestedUnderClass.wxs | 13 - .../TestData/ProgId/Package.en-us.wxl | 11 - .../TestData/ProgId/Package.wxs | 17 - .../TestData/ProgId/PackageComponents.wxs | 16 - .../TestData/ProgId/data/test.txt | 1 - .../TestData/PublishComponent/Package.en-us.wxl | 11 - .../TestData/PublishComponent/Package.wxs | 34 - .../TestData/PublishComponent/data/test.txt | 1 - .../Registry/DuplicateRegistryValueIds.wxs | 14 - .../Registry/RegistryKeyEndingWithBackslash.wxs | 12 - .../TestData/Registry/RegistryValue.wxs | 11 - .../TestData/Registry/RegistryValueMultiString.wxs | 17 - .../TestData/Registry/RemoveRegistryKey.wxs | 11 - .../TestData/ReserveCost/ReserveCost.wxs | 11 - .../TestData/RollbackBoundary/BeginningOfChain.wxs | 9 - .../TestData/SameFileFolders/TestComponents.wxs | 16 - .../TestData/SameFileFolders/data/a/test.txt | 1 - .../TestData/SameFileFolders/data/b/test.txt | 1 - .../TestData/SameFileFolders/data/c/test.txt | 1 - .../SequenceTables/DecompiledSequenceTables.wxs | 32 - .../TestData/SequenceTables/SequenceTables.msi | Bin 32768 -> 0 bytes .../TestData/ServiceInstall/OwnProcess.wxs | 12 - .../TestData/SetProperty/Package.en-us.wxl | 11 - .../TestData/SetProperty/Package.wxs | 19 - .../TestData/SetProperty/PackageComponents.wxs | 10 - .../TestData/SetProperty/data/test.txt | 1 - .../TestData/SetVariable/Simple.wxs | 15 - .../SharedPayloadsBetweenPackages.wxs | 18 - .../TestData/Shortcut/DecompiledShortcuts.wxs | 19 - .../TestData/Shortcut/ShortcutProperty.wxs | 14 - .../Shortcut/ShortcutSameNameShortName.wxs | 12 - .../TestData/Shortcut/shortcuts.msi | Bin 32768 -> 0 bytes .../TestData/SimpleBundle/Bundle.en-us.wxl | 10 - .../TestData/SimpleBundle/Bundle.wxs | 12 - .../MultiFileBootstrapperApplication.wxs | 7 - .../TestData/SimpleBundle/MultiFileBundle.wxs | 18 - .../SimpleBundle/data/MsiPackage/Shared.dll | 1 - .../TestData/SimpleBundle/data/MsiPackage/test.txt | 1 - .../TestData/SimpleBundle/data/fakeba.dll | 1 - .../TestData/SimpleBundle/data/test.msi | Bin 32768 -> 0 bytes .../TestData/SimpleMerge/.data/test.msm | Bin 24576 -> 0 bytes .../TestData/SimpleMerge/Package.en-us.wxl | 11 - .../TestData/SimpleMerge/Package.wxs | 20 - .../TestData/SimpleModule/Module.en-us.wxl | 10 - .../TestData/SimpleModule/Module.wixproj | 48 - .../TestData/SimpleModule/Module.wxs | 17 - .../TestData/SimpleModule/data/test.txt | 1 - .../SingleExeBundle/SingleExePackageGroup.wxs | 8 - .../SingleExeBundle/SingleExeRemotePayload.wxs | 27 - .../TestData/SingleFile/Package.en-us.wxl | 11 - .../TestData/SingleFile/Package.wxs | 17 - .../TestData/SingleFile/PackageComponents.wxs | 13 - .../TestData/SingleFile/data/test.txt | 1 - .../SingleFileCompressed/Package.en-us.wxl | 11 - .../TestData/SingleFileCompressed/Package.wxs | 25 - .../SingleFileCompressed/PackageComponents.wxs | 10 - .../TestData/SingleFileCompressed/data/test.txt | 1 - .../SuppressModularization/Module.en-us.wxl | 10 - .../TestData/SuppressModularization/Module.wxs | 10 - .../TestData/SuppressModularization/data/test.txt | 1 - .../TestData/TextStyle/ColorNull.wxs | 12 - .../TestData/TextStyle/SizeLocalized.en-us.wxl | 13 - .../TestData/TextStyle/SizeLocalized.wxs | 12 - .../TestData/TypeLib/Language0.wxs | 11 - .../TestData/Upgrade/DetectOnly.wxs | 12 - .../TestData/UsingProvides/Package.en-us.wxl | 11 - .../TestData/UsingProvides/Package.wxs | 16 - .../TestData/UsingProvides/PackageComponents.wxs | 10 - .../TestData/UsingProvides/example.txt | 1 - .../TestData/Variables/Package.en-us.wxl | 11 - .../TestData/Variables/Package.wxs | 31 - .../TestData/Variables/PackageComponents.wxs | 10 - .../TestData/Variables/data/test.txt | 1 - .../TestData/WixVariableOverride/Package.en-us.wxl | 11 - .../TestData/WixVariableOverride/Package.wxs | 19 - .../WixVariableOverride/PackageComponents.wxs | 14 - .../TestData/WixVariableOverride/data/test.txt | 1 - .../TestData/WixVariableOverride/data/test2.txt | 1 - .../TestData/Wixipl/Package.en-us.wxl | 11 - .../TestData/Wixipl/Package.wxs | 19 - .../TestData/Wixipl/PackageComponents.wxs | 10 - .../TestData/Wixipl/data/test.txt | 1 - .../TestData/WixlibWithBinaries/Package.en-us.wxl | 11 - .../TestData/WixlibWithBinaries/Package.wxs | 19 - .../WixlibWithBinaries/PackageComponents.wxs | 26 - .../TestData/WixlibWithBinaries/data/alpha/foo.dll | 1 - .../TestData/WixlibWithBinaries/data/mips/foo.dll | 1 - .../WixlibWithBinaries/data/powerpc/foo.dll | 1 - .../TestData/WixlibWithBinaries/data/test.txt | 1 - .../TestXmlFixture.cs | 62 - .../VariableResolverFixture.cs | 75 - .../WarningFixture.cs | 63 - .../WixToolsetTest.CoreIntegration.csproj | 32 - .../WixiplFixture.cs | 205 - .../WixlibFixture.cs | 316 - .../WixlibQueryFixture.cs | 81 - src/version.json | 11 + src/wix/Custom.Build.props | 6 + src/wix/Directory.Build.props | 27 + src/wix/Directory.Build.targets | 51 + src/wix/Directory.csproj.props | 13 + src/wix/Directory.csproj.targets | 26 + src/wix/README.md | 3 + .../WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | 27 + .../WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 650 ++ .../Bind/ExtensionSearchFacade.cs | 24 + .../Bind/GenerateManifestDataFromIRCommand.cs | 237 + .../Bind/LegacySearchFacade.cs | 185 + .../Bind/ProcessBundleSoftwareTagsCommand.cs | 133 + .../Bind/ProcessDependencyProvidersCommand.cs | 147 + .../Bind/ResolveDownloadUrlsCommand.cs | 128 + .../Bind/SetVariableSearchFacade.cs | 48 + src/wix/WixToolset.Core.Burn/BundleBackend.cs | 77 + .../AutomaticallySlipstreamPatchesCommand.cs | 117 + .../Bundles/BundleHashAlgorithm.cs | 30 + src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 385 + src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs | 212 + src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs | 245 + ...CreateBootstrapperApplicationManifestCommand.cs | 290 + .../Bundles/CreateBundleExeCommand.cs | 325 + .../CreateBundleExtensionManifestCommand.cs | 99 + .../Bundles/CreateBurnManifestCommand.cs | 700 ++ .../Bundles/CreateContainerCommand.cs | 70 + .../Bundles/CreateNonUXContainers.cs | 151 + .../Bundles/DetectPayloadCollisionsCommand.cs | 137 + .../Bundles/GetPackageFacadesCommand.cs | 181 + .../OrderPackagesAndRollbackBoundariesCommand.cs | 171 + .../Bundles/OrderSearchesCommand.cs | 367 + .../WixToolset.Core.Burn/Bundles/PackageFacade.cs | 25 + .../Bundles/ProcessExePackageCommand.cs | 39 + .../Bundles/ProcessMsiPackageCommand.cs | 558 ++ .../Bundles/ProcessMspPackageCommand.cs | 183 + .../Bundles/ProcessMsuPackageCommand.cs | 37 + .../Bundles/ProcessPayloadsCommand.cs | 108 + src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs | 72 + src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs | 30 + .../WixToolset.Core.Burn/BurnBackendWarnings.cs | 36 + .../WixToolset.Core.Burn/BurnExtensionFactory.cs | 22 + .../ExtensibilityServices/BurnBackendHelper.cs | 214 + .../ExtensibilityServices/PayloadHarvester.cs | 68 + .../IInternalBurnBackendHelper.cs | 14 + src/wix/WixToolset.Core.Burn/ISearchFacade.cs | 15 + .../Inscribe/InscribeBundleCommand.cs | 54 + .../Inscribe/InscribeBundleEngineCommand.cs | 63 + .../Interfaces/IPayloadHarvester.cs | 23 + src/wix/WixToolset.Core.Burn/RowIndexedList.cs | 299 + .../WixToolset.Core.Burn.csproj | 39 + .../WixToolsetCoreServiceProviderExtensions.cs | 45 + .../CachedExtension.cs | 20 + .../ExtensionCacheManager.cs | 248 + .../ExtensionCacheManagerCommand.cs | 181 + .../ExtensionCacheManagerExtensionCommandLine.cs | 41 + .../ExtensionCacheManagerExtensionFactory.cs | 30 + .../WixToolset.Core.ExtensionCache.csproj | 29 + .../WixToolsetCoreServiceProviderExtensions.cs | 36 + .../WixToolset.Core.TestPackage/BundleExtractor.cs | 139 + .../ExtractBAContainerResult.cs | 116 + .../TestMessageListener.cs | 55 + src/wix/WixToolset.Core.TestPackage/WixRunner.cs | 88 + .../WixToolset.Core.TestPackage/WixRunnerResult.cs | 62 + .../WixToolset.Core.TestPackage.csproj | 34 + .../XmlNodeExtensions.cs | 90 + .../Bind/AddBackSuppressedSequenceTablesCommand.cs | 52 + .../Bind/AddCreateFoldersCommand.cs | 38 + .../Bind/AddRequiredStandardDirectories.cs | 95 + .../Bind/AssemblyName.cs | 60 + .../Bind/AssemblyNameReader.cs | 214 + .../Bind/AssignMediaCommand.cs | 302 + .../Bind/AttachPatchTransformsCommand.cs | 1305 +++ .../Bind/BindDatabaseCommand.cs | 646 ++ .../Bind/BindSummaryInfoCommand.cs | 211 + .../Bind/BindTransformCommand.cs | 445 + .../Bind/CabinetBuilder.cs | 171 + .../Bind/CabinetResolver.cs | 132 + .../Bind/CabinetWorkItem.cs | 68 + .../Bind/CreateCabinetsCommand.cs | 455 ++ .../Bind/CreateDeltaPatchesCommand.cs | 81 + .../Bind/CreateIdtFileCommand.cs | 250 + .../Bind/CreateInstanceTransformsCommand.cs | 260 + .../Bind/CreatePatchTransformsCommand.cs | 93 + .../Bind/CreateSpecialPropertiesCommand.cs | 83 + .../CreateWindowsInstallerDataFromIRCommand.cs | 1621 ++++ .../Bind/ExtractMergeModuleFilesCommand.cs | 221 + .../Bind/FileSystemManager.cs | 77 + .../Bind/FinalizeComponentGuids.cs | 262 + .../Bind/GenerateDatabaseCommand.cs | 408 + .../Bind/GenerateTransformCommand.cs | 582 ++ .../Bind/GetFileFacadesCommand.cs | 157 + .../Bind/GetFileFacadesFromTransforms.cs | 174 + .../Bind/LoadTableDefinitionsCommand.cs | 215 + .../Bind/MergeModulesCommand.cs | 331 + .../Bind/ModularizeCommand.cs | 236 + .../Bind/OptimizeFileFacadesOrderCommand.cs | 119 + .../Bind/PatchTransform.cs | 19 + .../Bind/ProcessDependencyReferencesCommand.cs | 114 + .../Bind/ProcessPackageSoftwareTagsCommand.cs | 131 + .../Bind/ProcessPropertiesCommand.cs | 101 + .../Bind/ProcessUncompressedFilesCommand.cs | 125 + .../Bind/SequenceActionsCommand.cs | 714 ++ .../Bind/UpdateFileFacadesCommand.cs | 365 + .../Bind/UpdateFromTextFilesCommand.cs | 77 + .../Bind/UpdateMediaSequencesCommand.cs | 109 + .../Bind/UpdateTransformsWithFileFacades.cs | 451 ++ .../Bind/ValidateDatabaseCommand.cs | 187 + .../Decompile/DecompileMsiOrMsmCommand.cs | 96 + .../Decompile/Decompiler.cs | 7596 +++++++++++++++++ .../Decompile/Names.cs | 160 + src/wix/WixToolset.Core.WindowsInstaller/Differ.cs | 610 ++ .../WindowsInstallerBackendHelper.cs | 121 + .../Inscribe/InscribeMsiPackageCommand.cs | 272 + src/wix/WixToolset.Core.WindowsInstaller/Melter.cs | 399 + .../WixToolset.Core.WindowsInstaller/MelterCore.cs | 32 + .../WixToolset.Core.WindowsInstaller/MsiBackend.cs | 85 + .../WixToolset.Core.WindowsInstaller/MsmBackend.cs | 76 + .../WixToolset.Core.WindowsInstaller/MspBackend.cs | 162 + .../WixToolset.Core.WindowsInstaller/MstBackend.cs | 44 + .../RowDictionary.cs | 71 + .../Unbind/ExtractCabinetsCommand.cs | 147 + .../Unbind/UnbindDatabaseCommand.cs | 789 ++ .../Unbind/UnbindMsiOrMsmCommand.cs | 55 + .../Unbind/UnbindTranformCommand.cs | 309 + .../WindowsInstallerBackendErrors.cs | 24 + .../WindowsInstallerBackendFactory.cs | 51 + .../WindowsInstallerBackendWarnings.cs | 24 + .../WindowsInstallerExtensionFactory.cs | 22 + .../WixToolset.Core.WindowsInstaller.csproj | 30 + .../WixToolsetCoreServiceProviderExtensions.cs | 42 + src/wix/WixToolset.Core.sln | 156 + src/wix/WixToolset.Core.v3.ncrunchsolution | 6 + src/wix/WixToolset.Core/Bind/DelayedField.cs | 35 + .../WixToolset.Core/Bind/ExpectedExtractFile.cs | 16 + .../WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 92 + .../Bind/ExtractEmbeddedFilesCommand.cs | 53 + src/wix/WixToolset.Core/Bind/FileResolver.cs | 207 + .../Bind/ResolveDelayedFieldsCommand.cs | 164 + .../WixToolset.Core/Bind/ResolveFieldsCommand.cs | 276 + .../WixToolset.Core/Bind/TransferFilesCommand.cs | 196 + src/wix/WixToolset.Core/BindContext.cs | 65 + src/wix/WixToolset.Core/BindFileWithPath.cs | 22 + src/wix/WixToolset.Core/BindPath.cs | 20 + src/wix/WixToolset.Core/BindResult.cs | 48 + src/wix/WixToolset.Core/Binder.cs | 96 + .../WixToolset.Core/CommandLine/BuildCommand.cs | 912 +++ src/wix/WixToolset.Core/CommandLine/CommandLine.cs | 199 + .../CommandLine/CommandLineArguments.cs | 207 + .../CommandLine/CommandLineContext.cs | 22 + .../CommandLine/CommandLineParser.cs | 270 + .../WixToolset.Core/CommandLine/CompileCommand.cs | 94 + .../CommandLine/DecompileCommand.cs | 256 + src/wix/WixToolset.Core/CommandLine/HelpCommand.cs | 66 + .../WixToolset.Core/CommandLine/VersionCommand.cs | 26 + src/wix/WixToolset.Core/Common.cs | 832 ++ src/wix/WixToolset.Core/Compile/CompilerPayload.cs | 291 + src/wix/WixToolset.Core/CompileContext.cs | 34 + src/wix/WixToolset.Core/Compiler.cs | 8514 ++++++++++++++++++++ src/wix/WixToolset.Core/CompilerCore.cs | 1166 +++ src/wix/WixToolset.Core/CompilerErrors.cs | 43 + src/wix/WixToolset.Core/CompilerWarnings.cs | 65 + src/wix/WixToolset.Core/Compiler_Bundle.cs | 3266 ++++++++ src/wix/WixToolset.Core/Compiler_Dependency.cs | 384 + src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs | 417 + src/wix/WixToolset.Core/Compiler_Module.cs | 662 ++ src/wix/WixToolset.Core/Compiler_Package.cs | 4996 ++++++++++++ src/wix/WixToolset.Core/Compiler_Patch.cs | 657 ++ src/wix/WixToolset.Core/Compiler_PatchCreation.cs | 1265 +++ src/wix/WixToolset.Core/Compiler_Tag.cs | 315 + src/wix/WixToolset.Core/Compiler_UI.cs | 1808 +++++ src/wix/WixToolset.Core/ComponentKeyPath.cs | 25 + src/wix/WixToolset.Core/DecompileContext.cs | 49 + src/wix/WixToolset.Core/DecompileResult.cs | 18 + src/wix/WixToolset.Core/Decompiler.cs | 68 + .../ExtensibilityServices/BackendHelper.cs | 176 + .../ExtensibilityServices/ExtensionManager.cs | 233 + .../ExtensibilityServices/FileFacade.cs | 172 + .../ExtensibilityServices/FileTransfer.cs | 20 + .../ExtensibilityServices/Messaging.cs | 99 + .../ExtensibilityServices/ParseHelper.cs | 863 ++ .../ExtensibilityServices/PathResolver.cs | 118 + .../ExtensibilityServices/PreprocessHelper.cs | 499 ++ .../ExtensibilityServices/ResolvedDirectory.cs | 15 + .../SymbolDefinitionCreator.cs | 70 + .../ExtensibilityServices/TrackedFile.cs | 26 + .../WixToolset.Core/ExtensibilityServices/Uuid.cs | 81 + .../ExtensibilityServices/WixBranding.cs | 124 + src/wix/WixToolset.Core/IBinder.cs | 12 + src/wix/WixToolset.Core/ICompiler.cs | 13 + src/wix/WixToolset.Core/IDecompiler.cs | 12 + src/wix/WixToolset.Core/ILayoutCreator.cs | 12 + src/wix/WixToolset.Core/ILibrarian.cs | 13 + src/wix/WixToolset.Core/ILinker.cs | 13 + src/wix/WixToolset.Core/ILocalizationParser.cs | 27 + src/wix/WixToolset.Core/IPreprocessor.cs | 15 + src/wix/WixToolset.Core/IResolver.cs | 19 + src/wix/WixToolset.Core/IUnbinder.cs | 12 + src/wix/WixToolset.Core/IncludedFile.cs | 14 + src/wix/WixToolset.Core/IncribeContext.cs | 26 + src/wix/WixToolset.Core/LayoutContext.cs | 40 + src/wix/WixToolset.Core/LayoutCreator.cs | 223 + src/wix/WixToolset.Core/Librarian.cs | 135 + src/wix/WixToolset.Core/LibraryContext.cs | 38 + .../Link/CollateLocalizationsCommand.cs | 71 + src/wix/WixToolset.Core/Link/ConnectToFeature.cs | 59 + .../Link/ConnectToFeatureCollection.cs | 92 + src/wix/WixToolset.Core/Link/ConnectToModule.cs | 54 + .../Link/ConnectToModuleCollection.cs | 92 + .../Link/FindEntrySectionAndLoadSymbolsCommand.cs | 119 + .../Link/FlattenAndProcessBundleTablesCommand.cs | 194 + .../Link/IntermediateSymbolExtensions.cs | 26 + .../Link/ReportConflictingSymbolsCommand.cs | 54 + .../Link/ResolveReferencesCommand.cs | 183 + src/wix/WixToolset.Core/Link/SymbolWithSection.cs | 92 + .../Link/WixComplexReferenceSymbolExtensions.cs | 75 + .../WixToolset.Core/Link/WixGroupingOrdering.cs | 683 ++ src/wix/WixToolset.Core/LinkContext.cs | 33 + src/wix/WixToolset.Core/Linker.cs | 942 +++ src/wix/WixToolset.Core/LinkerErrors.cs | 48 + src/wix/WixToolset.Core/LinkerWarnings.cs | 36 + src/wix/WixToolset.Core/LocalizationParser.cs | 326 + src/wix/WixToolset.Core/ParsedWixVariable.cs | 19 + src/wix/WixToolset.Core/Preprocess/IfContext.cs | 74 + .../Preprocess/IfDefEventHandler.cs | 28 + src/wix/WixToolset.Core/Preprocess/IfState.cs | 22 + .../Preprocess/IncludedFileEventHandler.cs | 43 + .../Preprocess/PreprocessorOperation.cs | 19 + .../Preprocess/ProcessedStreamEventHandler.cs | 43 + .../Preprocess/ResolvedVariableEventHandler.cs | 25 + src/wix/WixToolset.Core/PreprocessContext.cs | 35 + src/wix/WixToolset.Core/PreprocessResult.cs | 15 + src/wix/WixToolset.Core/Preprocessor.cs | 1520 ++++ src/wix/WixToolset.Core/Properties/AssemblyInfo.cs | 9 + src/wix/WixToolset.Core/ResolveContext.cs | 42 + src/wix/WixToolset.Core/ResolveFileResult.cs | 14 + src/wix/WixToolset.Core/ResolveResult.cs | 23 + src/wix/WixToolset.Core/ResolvedCabinet.cs | 22 + src/wix/WixToolset.Core/Resolver.cs | 304 + src/wix/WixToolset.Core/SourceFile.cs | 17 + src/wix/WixToolset.Core/UnbindContext.cs | 29 + src/wix/WixToolset.Core/Unbinder.cs | 99 + src/wix/WixToolset.Core/VariableResolution.cs | 29 + src/wix/WixToolset.Core/VariableResolver.cs | 197 + src/wix/WixToolset.Core/WixToolset.Core.csproj | 47 + .../WixToolset.Core.v3.ncrunchproject | 7 + .../WixToolset.Core/WixToolsetServiceProvider.cs | 117 + .../WixToolsetServiceProviderFactory.cs | 21 + src/wix/appveyor.cmd | 20 + src/wix/appveyor.yml | 44 + src/wix/nuget.config | 13 + .../CompileCoreTestExtensionWixlib.csproj | 32 + .../test/CompileCoreTestExtensionWixlib/Program.cs | 37 + src/wix/test/Example.Extension/Data/example.txt | 1 + src/wix/test/Example.Extension/Data/example.wxs | 15 + .../Example.Extension/Example.Extension.csproj | 24 + .../Example.Extension/ExampleCompilerExtension.cs | 195 + .../test/Example.Extension/ExampleExtensionData.cs | 23 + .../Example.Extension/ExampleExtensionFactory.cs | 54 + .../ExamplePreprocessorExtensionAndCommandLine.cs | 57 + src/wix/test/Example.Extension/ExampleRow.cs | 32 + .../test/Example.Extension/ExampleSearchSymbol.cs | 30 + src/wix/test/Example.Extension/ExampleSymbol.cs | 30 + .../Example.Extension/ExampleSymbolDefinitions.cs | 67 + .../Example.Extension/ExampleTableDefinitions.cs | 34 + .../ExampleWindowsInstallerBackendExtension.cs | 33 + .../WixToolsetTest.Core.Burn/BurnReaderFixture.cs | 44 + .../WixToolsetTest.Core.Burn.csproj | 28 + .../ApprovedExeFixture.cs | 64 + .../BadInputFixture.cs | 148 + .../BindVariablesFixture.cs | 96 + .../BootstrapperApplicationFixture.cs | 46 + .../BundleExtractionFixture.cs | 58 + .../BundleFixture.cs | 478 ++ .../BundleManifestFixture.cs | 365 + .../WixToolsetTest.CoreIntegration/CabFixture.cs | 107 + .../ComponentFixture.cs | 45 + .../ContainerFixture.cs | 385 + .../CopyFileFixture.cs | 48 + .../CustomActionFixture.cs | 169 + .../CustomTableFixture.cs | 234 + .../DecompileFixture.cs | 86 + .../DependencyExtensionFixture.cs | 180 + .../DirectoryFixture.cs | 271 + .../ExePackageFixture.cs | 52 + .../ExtensionFixture.cs | 153 + .../LanguageFixture.cs | 174 + .../LinkerFixture.cs | 174 + .../WixToolsetTest.CoreIntegration/MediaFixture.cs | 62 + .../ModuleFixture.cs | 113 + .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 838 ++ .../MsiQueryFixture.cs | 1040 +++ .../MsiTransactionFixture.cs | 131 + .../MsuPackageFixture.cs | 36 + .../PackagePayloadFixture.cs | 211 + .../WixToolsetTest.CoreIntegration/ParseFixture.cs | 36 + .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 279 + .../PayloadFixture.cs | 212 + .../PreprocessorFixture.cs | 181 + .../RegistryFixture.cs | 173 + .../RollbackBoundaryFixture.cs | 41 + .../ShortcutFixture.cs | 78 + .../SoftwareTagFixture.cs | 100 + .../TestData/.Data/burn.exe | Bin 0 -> 463360 bytes .../TestData/AppId/Advertised.wxs | 11 + .../TestData/AppSearch/ComponentSearch.wxs | 12 + .../DecompiledNestedDirSearchUnderRegSearch.wxs | 42 + .../TestData/AppSearch/DirectorySearch.wxs | 12 + .../TestData/AppSearch/FileSearch.wxs | 14 + .../AppSearch/NestedDirSearchUnderRegSearch.msi | Bin 0 -> 33045 bytes .../TestData/AppSearch/RegistrySearch.wxs | 12 + .../TestData/AppSearch/RegistrySearch64.wxs | 12 + .../TestData/Assembly/Package.en-us.wxl | 11 + .../TestData/Assembly/Package.wxs | 17 + .../TestData/Assembly/PackageComponents.wxs | 10 + .../TestData/Assembly/Win32Assembly.wxs | 13 + .../TestData/Assembly/data/candle.exe | Bin 0 -> 28672 bytes .../TestData/Assembly/data/test.manifest | 76 + .../TestData/BadEnsureTable/BadEnsureTable.wxs | 11 + .../TestData/BadIf/Package.en-us.wxl | 11 + .../TestData/BadIf/Package.wxs | 24 + .../TestData/BadIf/PackageComponents.wxs | 12 + .../TestData/BadIf/data/test.txt | 1 + .../TestData/BadInput/BundleVariable.wxs | 6 + .../TestData/BadInput/DuplicateCacheIds.wxs | 12 + .../TestData/BadInput/DuplicatePayloadNames.wxs | 31 + .../BadInput/HiddenPersistedBundleVariable.wxs | 6 + .../TestData/BadInput/InvalidIds.wxs | 8 + .../TestData/BadInput/OrphanPayload.wxs | 11 + .../BadInput/PackageInMultipleContainers.wxs | 14 + .../TestData/BadInput/RegistryKey.wxs | 13 + .../TestData/BadInput/UnscheduledPackage.wxs | 16 + .../BadInput/UnscheduledRollbackBoundary.wxs | 16 + .../TestData/BindVariables/DefaultedVariable.wxs | 6 + .../TestData/BindVariables/data/test.txt | 1 + .../BootstrapperApplication/DpiAwareness.wxs | 7 + .../CacheIdFromPackageDescription.wxs | 8 + .../BundleCustomTable/BundleCustomTable.wxs | 53 + .../TestData/BundleExtension/BundleExtension.wxs | 6 + .../BundleExtension/BundleExtensionSearches.wxs | 8 + .../BundleExtension/BundleWithSearches.wxs | 11 + .../BundleExtension/SimpleBundleExtension.wxs | 10 + .../TestData/BundleTag/BundleWithTag.wxs | 15 + .../TestData/BundleTag/fakeba.dll | 1 + .../TestData/BundleWithApprovedExe/Bundle.wxs | 5 + .../TestData/BundleWithApprovedExe/Bundle64.wxs | 5 + .../BundleWithDetachedContainer/Bundle.wxs | 10 + .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 10 + .../MinimalPackageGroup.wxs | 8 + .../TestData/Class/DecompiledOldClassTableDef.wxs | 22 + .../TestData/Class/IconIndex0.wxs | 11 + .../TestData/Class/OldClassTableDef.msi | Bin 0 -> 36864 bytes .../ComplexExampleExtension/OtherComponents.wxs | 12 + .../ComplexExampleExtension/Package.en-us.wxl | 11 + .../TestData/ComplexExampleExtension/Package.wxs | 22 + .../ComplexExampleExtension/PackageComponents.wxs | 12 + .../ComplexExampleExtension/data/example.txt | 1 + .../ComplexExampleExtension/data/other.txt | 1 + .../TestData/Component/GuidCollision.wxs | 14 + .../TestData/Components/Package.en-us.wxl | 11 + .../TestData/Components/Package.wxs | 17 + .../TestData/Components/PackageComponents.wxs | 10 + .../TestData/Components/data/test.txt | 1 + .../Container/HarvestIntoAttachedContainer.wxs | 17 + .../Container/HarvestIntoDetachedContainer.wxs | 15 + .../Container/LayoutPayloadInContainer.wxs | 28 + .../Container/MultipleAttachedContainers.wxs | 15 + .../Container/PayloadInMultipleContainers.wxs | 28 + .../TestData/CopyFile/CopyFile.wxs | 17 + .../TestData/CustomAction/CustomActionCycle.wxs | 18 + .../CustomAction/CustomActionCycleWithTail.wxs | 20 + .../TestData/CustomAction/SimpleCustomAction.wxs | 14 + .../CustomAction/UnscheduledCustomAction.wxs | 32 + .../CustomPackageDescription.wxs | 12 + .../TestData/CustomTable/CustomTable-Expected.wxs | 29 + .../TestData/CustomTable/CustomTable.wxs | 34 + .../TestData/CustomTable/CustomTableWithFile.wxs | 22 + .../CustomTable/LocalizedCustomTable.en-us.wxl | 7 + .../TestData/CustomTable/LocalizedCustomTable.wxs | 21 + .../TestData/CustomTable/data/file1.txt | 1 + .../TestData/CustomTable/data/file2.txt | 1 + .../TestData/CustomTable/data/test.txt | 1 + .../TestData/DecompileNullComponent/Expected.wxs | 16 + .../TestData/DecompileNullComponent/example.cab | Bin 0 -> 137 bytes .../TestData/DecompileNullComponent/example.msi | Bin 0 -> 32768 bytes .../DecompileSingleFileCompressed/Expected.wxs | 16 + .../DecompileSingleFileCompressed/example.cab | Bin 0 -> 137 bytes .../DecompileSingleFileCompressed/example.msi | Bin 0 -> 32768 bytes .../DecompileSingleFileCompressed64/Expected.wxs | 16 + .../DecompileSingleFileCompressed64/example.cab | Bin 0 -> 137 bytes .../DecompileSingleFileCompressed64/example.msi | Bin 0 -> 32768 bytes .../DecompileTargetDirMergeModule/Expected.wxs | 18 + .../DecompileTargetDirMergeModule/MergeModule1.msm | Bin 0 -> 32768 bytes .../TestData/DefaultDir/DefaultDir.wxs | 26 + .../Dependency/CustomProviderKeyBundle.wxs | 10 + .../Dependency/ExePackageProvidesBundle.wxs | 10 + .../TestData/Dependency/UsingProvidesBundle.wxs | 8 + .../PackageComponents.wxs | 36 + .../TestData/Directory/DefaultName.wxs | 13 + .../Directory/DuplicateTargetSourceName.wxs | 11 + .../TestData/Directory/Empty.wxs | 6 + .../TestData/Directory/Nested.wxs | 11 + .../TestData/DuplicateDir/DuplicateDir.wxs | 25 + .../TestData/EnsureTable/EnsureTable.wxs | 10 + .../TestData/Environment/Environment.wxs | 14 + .../TestData/ErrorsInUI/Package.en-us.wxl | 9 + .../TestData/ErrorsInUI/Package.wxs | 20 + .../TestData/ErrorsInUI/PackageComponents.wxs | 14 + .../TestData/ErrorsInUI/data/test.txt | 1 + .../TestData/ExampleExtension/Package.en-us.wxl | 11 + .../TestData/ExampleExtension/Package.wxs | 21 + .../ExampleExtension/PackageComponents.wxs | 12 + .../TestData/ExampleExtension/data/example.txt | 1 + .../TestData/ExePackage/MissingDetectCondition.wxs | 9 + .../TestData/ExePackage/RequireDetectCondition.wxs | 11 + .../TestData/FeatureGroup/FeatureGroup.wxs | 14 + .../TestData/Font/FontTitle.wxs | 10 + .../TestData/Font/TrueType.wxs | 10 + .../TestData/ForEach/Package.en-us.wxl | 11 + .../TestData/ForEach/Package.wxs | 19 + .../TestData/ForEach/PackageComponents.wxs | 12 + .../TestData/ForEach/data/test.txt | 1 + .../TestData/Icon/SampleIcon.wxs | 6 + .../TestData/IncludePath/Package.en-us.wxl | 11 + .../TestData/IncludePath/Package.wxs | 18 + .../TestData/IncludePath/PackageComponents.wxs | 8 + .../TestData/IncludePath/data/DontDoThis.wxi | 6 + .../TestData/IncludePath/data/Package.wxi | 4 + .../TestData/IncludePath/data/test.txt | 1 + .../TestData/InstanceTransform/Package.en-us.wxl | 11 + .../TestData/InstanceTransform/Package.wxs | 23 + .../InstanceTransform/PackageComponents.wxs | 10 + .../TestData/InstanceTransform/data/test.txt | 1 + .../TestData/Language/Package.en-us.wxl | 7 + .../TestData/Language/Package.ja-jp.wxl | 7 + .../TestData/Language/Package.wxl | 7 + .../TestData/Language/Package.wxs | 18 + .../Language/PackageWithEnSummaryInfo.ja-jp.wxl | 7 + .../TestData/Language/data/test.txt | 1 + .../TestData/LockPermissions/EmptyPermissions.wxs | 13 + .../TestData/ManualUpgrade/Package.en-us.wxl | 11 + .../TestData/ManualUpgrade/Package.wxs | 24 + .../TestData/ManualUpgrade/PackageComponents.wxs | 10 + .../TestData/ManualUpgrade/data/test.txt | 1 + .../TestData/Media/MultiMedia.wxs | 28 + .../TestData/Media/data/a1.txt | 1 + .../TestData/Media/data/a2.txt | 1 + .../TestData/Media/data/b1.txt | 1 + .../TestData/Media/data/b2.txt | 1 + .../TestData/MsiTransaction/FirstX64.wxs | 8 + .../TestData/MsiTransaction/FirstX86.wxs | 8 + .../TestData/MsiTransaction/SecondX64.wxs | 8 + .../TestData/MsiTransaction/SecondX86.wxs | 8 + .../TestData/MsiTransaction/X64AfterX86Bundle.wxs | 12 + .../TestData/MsiTransaction/X86AfterX64Bundle.wxs | 12 + .../TestData/MsuPackage/Bundle.wxs | 11 + .../TestData/MsuPackage/data/fakeba.dll | 1 + .../TestData/MsuPackage/data/test.msu | 1 + .../TestData/MultiFileCompressed/Package.en-us.wxl | 11 + .../TestData/MultiFileCompressed/Package.wxs | 26 + .../MultiFileCompressed/PackageComponents.wxs | 13 + .../TestData/MultiFileCompressed/data/test.txt | 1 + .../TestData/OverridableActions/Package.en-us.wxl | 11 + .../TestData/OverridableActions/Package.wxs | 48 + .../OverridableActions/PackageComponents.wxs | 10 + .../TestData/OverridableActions/data/test.txt | 1 + .../PackagePayload/MissingSourceFileAndHash.wxs | 10 + .../PackagePayload/MissingSourceFileAndName.wxs | 10 + .../PackagePayloadInPayloadGroup.wxs | 15 + .../TestData/PackagePayload/SpecifiedHash.wxs | 10 + .../SpecifiedHashAndMissingDownloadUrl.wxs | 10 + .../PackagePayload/SpecifiedSourceFileAndHash.wxs | 10 + .../WrongPackagePayloadInPayloadGroup.wxs | 15 + .../TestData/PatchFamilyFilter/.data/Av1.0.0.txt | 1 + .../TestData/PatchFamilyFilter/.data/Av1.0.1.txt | 1 + .../TestData/PatchFamilyFilter/.data/Bv1.0.0.txt | 1 + .../TestData/PatchFamilyFilter/.data/Bv1.0.1.txt | 1 + .../TestData/PatchFamilyFilter/Package.wxs | 28 + .../TestData/PatchFamilyFilter/Patch.wxs | 16 + .../TestData/PatchFromWixlib/Package.wxs | 30 + .../TestData/PatchFromWixlib/Patch.wxs | 16 + .../TestData/PatchNoFileChanges/.data/A.txt | 1 + .../TestData/PatchNoFileChanges/Package.wxs | 27 + .../TestData/PatchNoFileChanges/Patch.wxs | 16 + .../TestData/PatchNonSpecific/BundleA/Bundle.wxs | 7 + .../TestData/PatchNonSpecific/BundleB/Bundle.wxs | 8 + .../TestData/PatchNonSpecific/BundleC/Bundle.wxs | 9 + .../TestData/PatchNonSpecific/PackageA/Package.wxs | 44 + .../TestData/PatchNonSpecific/PatchA/Patch.wxs | 12 + .../TestData/PatchNonSpecific/PatchB/Patch.wxs | 14 + .../TestData/PatchNonSpecific/PatchC/Patch.wxs | 14 + .../TestData/PatchSingle/.data/Av1.0.0.txt | 1 + .../TestData/PatchSingle/.data/Av1.0.1.txt | 1 + .../TestData/PatchSingle/.data/Bv1.0.0.txt | 1 + .../TestData/PatchSingle/.data/Bv1.0.1.txt | 1 + .../TestData/PatchSingle/BundleA/Bundle.wxs | 10 + .../TestData/PatchSingle/Package.wxs | 27 + .../TestData/PatchSingle/Patch.wxs | 16 + .../TestData/Payload/AbsoluteName.wxs | 9 + .../TestData/Payload/CanonicalizeName.wxs | 7 + .../Payload/DownloadUrlPlaceholdersBundle.wxs | 30 + .../Payload/SharedBAAndPackagePayloadBundle.wxs | 16 + .../TestData/Payload/ValidName.wxs | 7 + .../TestData/Preprocessor/EnvParens.wxs | 4 + .../TestData/ProductTag/Package.en-us.wxl | 11 + .../TestData/ProductTag/PackageComponents.wxs | 10 + .../TestData/ProductTag/PackageWithTag.wxs | 18 + .../TestData/ProductTag/example.txt | 1 + .../MinimalComponentGroup.wxs | 10 + .../ProductWithComponentGroupRef/Product.wxs | 19 + .../TestData/ProgId/NestedUnderClass.wxs | 13 + .../TestData/ProgId/Package.en-us.wxl | 11 + .../TestData/ProgId/Package.wxs | 17 + .../TestData/ProgId/PackageComponents.wxs | 16 + .../TestData/ProgId/data/test.txt | 1 + .../TestData/PublishComponent/Package.en-us.wxl | 11 + .../TestData/PublishComponent/Package.wxs | 34 + .../TestData/PublishComponent/data/test.txt | 1 + .../Registry/DuplicateRegistryValueIds.wxs | 14 + .../Registry/RegistryKeyEndingWithBackslash.wxs | 12 + .../TestData/Registry/RegistryValue.wxs | 11 + .../TestData/Registry/RegistryValueMultiString.wxs | 17 + .../TestData/Registry/RemoveRegistryKey.wxs | 11 + .../TestData/ReserveCost/ReserveCost.wxs | 11 + .../TestData/RollbackBoundary/BeginningOfChain.wxs | 9 + .../TestData/SameFileFolders/TestComponents.wxs | 16 + .../TestData/SameFileFolders/data/a/test.txt | 1 + .../TestData/SameFileFolders/data/b/test.txt | 1 + .../TestData/SameFileFolders/data/c/test.txt | 1 + .../SequenceTables/DecompiledSequenceTables.wxs | 32 + .../TestData/SequenceTables/SequenceTables.msi | Bin 0 -> 32768 bytes .../TestData/ServiceInstall/OwnProcess.wxs | 12 + .../TestData/SetProperty/Package.en-us.wxl | 11 + .../TestData/SetProperty/Package.wxs | 19 + .../TestData/SetProperty/PackageComponents.wxs | 10 + .../TestData/SetProperty/data/test.txt | 1 + .../TestData/SetVariable/Simple.wxs | 15 + .../SharedPayloadsBetweenPackages.wxs | 18 + .../TestData/Shortcut/DecompiledShortcuts.wxs | 19 + .../TestData/Shortcut/ShortcutProperty.wxs | 14 + .../Shortcut/ShortcutSameNameShortName.wxs | 12 + .../TestData/Shortcut/shortcuts.msi | Bin 0 -> 32768 bytes .../TestData/SimpleBundle/Bundle.en-us.wxl | 10 + .../TestData/SimpleBundle/Bundle.wxs | 12 + .../MultiFileBootstrapperApplication.wxs | 7 + .../TestData/SimpleBundle/MultiFileBundle.wxs | 18 + .../SimpleBundle/data/MsiPackage/Shared.dll | 1 + .../TestData/SimpleBundle/data/MsiPackage/test.txt | 1 + .../TestData/SimpleBundle/data/fakeba.dll | 1 + .../TestData/SimpleBundle/data/test.msi | Bin 0 -> 32768 bytes .../TestData/SimpleMerge/.data/test.msm | Bin 0 -> 24576 bytes .../TestData/SimpleMerge/Package.en-us.wxl | 11 + .../TestData/SimpleMerge/Package.wxs | 20 + .../TestData/SimpleModule/Module.en-us.wxl | 10 + .../TestData/SimpleModule/Module.wixproj | 48 + .../TestData/SimpleModule/Module.wxs | 17 + .../TestData/SimpleModule/data/test.txt | 1 + .../SingleExeBundle/SingleExePackageGroup.wxs | 8 + .../SingleExeBundle/SingleExeRemotePayload.wxs | 27 + .../TestData/SingleFile/Package.en-us.wxl | 11 + .../TestData/SingleFile/Package.wxs | 17 + .../TestData/SingleFile/PackageComponents.wxs | 13 + .../TestData/SingleFile/data/test.txt | 1 + .../SingleFileCompressed/Package.en-us.wxl | 11 + .../TestData/SingleFileCompressed/Package.wxs | 25 + .../SingleFileCompressed/PackageComponents.wxs | 10 + .../TestData/SingleFileCompressed/data/test.txt | 1 + .../SuppressModularization/Module.en-us.wxl | 10 + .../TestData/SuppressModularization/Module.wxs | 10 + .../TestData/SuppressModularization/data/test.txt | 1 + .../TestData/TextStyle/ColorNull.wxs | 12 + .../TestData/TextStyle/SizeLocalized.en-us.wxl | 13 + .../TestData/TextStyle/SizeLocalized.wxs | 12 + .../TestData/TypeLib/Language0.wxs | 11 + .../TestData/Upgrade/DetectOnly.wxs | 12 + .../TestData/UsingProvides/Package.en-us.wxl | 11 + .../TestData/UsingProvides/Package.wxs | 16 + .../TestData/UsingProvides/PackageComponents.wxs | 10 + .../TestData/UsingProvides/example.txt | 1 + .../TestData/Variables/Package.en-us.wxl | 11 + .../TestData/Variables/Package.wxs | 31 + .../TestData/Variables/PackageComponents.wxs | 10 + .../TestData/Variables/data/test.txt | 1 + .../TestData/WixVariableOverride/Package.en-us.wxl | 11 + .../TestData/WixVariableOverride/Package.wxs | 19 + .../WixVariableOverride/PackageComponents.wxs | 14 + .../TestData/WixVariableOverride/data/test.txt | 1 + .../TestData/WixVariableOverride/data/test2.txt | 1 + .../TestData/Wixipl/Package.en-us.wxl | 11 + .../TestData/Wixipl/Package.wxs | 19 + .../TestData/Wixipl/PackageComponents.wxs | 10 + .../TestData/Wixipl/data/test.txt | 1 + .../TestData/WixlibWithBinaries/Package.en-us.wxl | 11 + .../TestData/WixlibWithBinaries/Package.wxs | 19 + .../WixlibWithBinaries/PackageComponents.wxs | 26 + .../TestData/WixlibWithBinaries/data/alpha/foo.dll | 1 + .../TestData/WixlibWithBinaries/data/mips/foo.dll | 1 + .../WixlibWithBinaries/data/powerpc/foo.dll | 1 + .../TestData/WixlibWithBinaries/data/test.txt | 1 + .../TestXmlFixture.cs | 62 + .../VariableResolverFixture.cs | 75 + .../WarningFixture.cs | 63 + .../WixToolsetTest.CoreIntegration.csproj | 32 + .../WixiplFixture.cs | 205 + .../WixlibFixture.cs | 316 + .../WixlibQueryFixture.cs | 81 + version.json | 11 - 1209 files changed, 80836 insertions(+), 80738 deletions(-) delete mode 100644 .editorconfig delete mode 100644 README.md delete mode 100644 WixToolset.Core.sln delete mode 100644 WixToolset.Core.v3.ncrunchsolution delete mode 100644 appveyor.cmd delete mode 100644 appveyor.yml delete mode 100644 nuget.config create mode 100644 src/.editorconfig delete mode 100644 src/Custom.Build.props delete mode 100644 src/Directory.Build.props delete mode 100644 src/Directory.Build.targets delete mode 100644 src/Directory.csproj.props delete mode 100644 src/Directory.csproj.targets delete mode 100644 src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/BundleBackend.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BurnCommon.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BurnReader.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BurnWriter.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/PackageFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/BurnBackendErrors.cs delete mode 100644 src/WixToolset.Core.Burn/BurnBackendFactory.cs delete mode 100644 src/WixToolset.Core.Burn/BurnBackendWarnings.cs delete mode 100644 src/WixToolset.Core.Burn/BurnExtensionFactory.cs delete mode 100644 src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs delete mode 100644 src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs delete mode 100644 src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs delete mode 100644 src/WixToolset.Core.Burn/ISearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs delete mode 100644 src/WixToolset.Core.Burn/RowIndexedList.cs delete mode 100644 src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj delete mode 100644 src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/CachedExtension.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj delete mode 100644 src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs delete mode 100644 src/WixToolset.Core.TestPackage/BundleExtractor.cs delete mode 100644 src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs delete mode 100644 src/WixToolset.Core.TestPackage/TestMessageListener.cs delete mode 100644 src/WixToolset.Core.TestPackage/WixRunner.cs delete mode 100644 src/WixToolset.Core.TestPackage/WixRunnerResult.cs delete mode 100644 src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj delete mode 100644 src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Differ.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Melter.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MelterCore.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MsiBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MsmBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MspBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MstBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/RowDictionary.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj delete mode 100644 src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs delete mode 100644 src/WixToolset.Core/Bind/DelayedField.cs delete mode 100644 src/WixToolset.Core/Bind/ExpectedExtractFile.cs delete mode 100644 src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs delete mode 100644 src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/FileResolver.cs delete mode 100644 src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs delete mode 100644 src/WixToolset.Core/Bind/ResolveFieldsCommand.cs delete mode 100644 src/WixToolset.Core/Bind/TransferFilesCommand.cs delete mode 100644 src/WixToolset.Core/BindContext.cs delete mode 100644 src/WixToolset.Core/BindFileWithPath.cs delete mode 100644 src/WixToolset.Core/BindPath.cs delete mode 100644 src/WixToolset.Core/BindResult.cs delete mode 100644 src/WixToolset.Core/Binder.cs delete mode 100644 src/WixToolset.Core/CommandLine/BuildCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLine.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineArguments.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineContext.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineParser.cs delete mode 100644 src/WixToolset.Core/CommandLine/CompileCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/DecompileCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/HelpCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/VersionCommand.cs delete mode 100644 src/WixToolset.Core/Common.cs delete mode 100644 src/WixToolset.Core/Compile/CompilerPayload.cs delete mode 100644 src/WixToolset.Core/CompileContext.cs delete mode 100644 src/WixToolset.Core/Compiler.cs delete mode 100644 src/WixToolset.Core/CompilerCore.cs delete mode 100644 src/WixToolset.Core/CompilerErrors.cs delete mode 100644 src/WixToolset.Core/CompilerWarnings.cs delete mode 100644 src/WixToolset.Core/Compiler_Bundle.cs delete mode 100644 src/WixToolset.Core/Compiler_Dependency.cs delete mode 100644 src/WixToolset.Core/Compiler_EmbeddedUI.cs delete mode 100644 src/WixToolset.Core/Compiler_Module.cs delete mode 100644 src/WixToolset.Core/Compiler_Package.cs delete mode 100644 src/WixToolset.Core/Compiler_Patch.cs delete mode 100644 src/WixToolset.Core/Compiler_PatchCreation.cs delete mode 100644 src/WixToolset.Core/Compiler_Tag.cs delete mode 100644 src/WixToolset.Core/Compiler_UI.cs delete mode 100644 src/WixToolset.Core/ComponentKeyPath.cs delete mode 100644 src/WixToolset.Core/DecompileContext.cs delete mode 100644 src/WixToolset.Core/DecompileResult.cs delete mode 100644 src/WixToolset.Core/Decompiler.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/FileFacade.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/Messaging.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/PathResolver.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/Uuid.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/WixBranding.cs delete mode 100644 src/WixToolset.Core/IBinder.cs delete mode 100644 src/WixToolset.Core/ICompiler.cs delete mode 100644 src/WixToolset.Core/IDecompiler.cs delete mode 100644 src/WixToolset.Core/ILayoutCreator.cs delete mode 100644 src/WixToolset.Core/ILibrarian.cs delete mode 100644 src/WixToolset.Core/ILinker.cs delete mode 100644 src/WixToolset.Core/ILocalizationParser.cs delete mode 100644 src/WixToolset.Core/IPreprocessor.cs delete mode 100644 src/WixToolset.Core/IResolver.cs delete mode 100644 src/WixToolset.Core/IUnbinder.cs delete mode 100644 src/WixToolset.Core/IncludedFile.cs delete mode 100644 src/WixToolset.Core/IncribeContext.cs delete mode 100644 src/WixToolset.Core/LayoutContext.cs delete mode 100644 src/WixToolset.Core/LayoutCreator.cs delete mode 100644 src/WixToolset.Core/Librarian.cs delete mode 100644 src/WixToolset.Core/LibraryContext.cs delete mode 100644 src/WixToolset.Core/Link/CollateLocalizationsCommand.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToFeature.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToFeatureCollection.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToModule.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToModuleCollection.cs delete mode 100644 src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs delete mode 100644 src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs delete mode 100644 src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs delete mode 100644 src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs delete mode 100644 src/WixToolset.Core/Link/ResolveReferencesCommand.cs delete mode 100644 src/WixToolset.Core/Link/SymbolWithSection.cs delete mode 100644 src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs delete mode 100644 src/WixToolset.Core/Link/WixGroupingOrdering.cs delete mode 100644 src/WixToolset.Core/LinkContext.cs delete mode 100644 src/WixToolset.Core/Linker.cs delete mode 100644 src/WixToolset.Core/LinkerErrors.cs delete mode 100644 src/WixToolset.Core/LinkerWarnings.cs delete mode 100644 src/WixToolset.Core/LocalizationParser.cs delete mode 100644 src/WixToolset.Core/ParsedWixVariable.cs delete mode 100644 src/WixToolset.Core/Preprocess/IfContext.cs delete mode 100644 src/WixToolset.Core/Preprocess/IfDefEventHandler.cs delete mode 100644 src/WixToolset.Core/Preprocess/IfState.cs delete mode 100644 src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs delete mode 100644 src/WixToolset.Core/Preprocess/PreprocessorOperation.cs delete mode 100644 src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs delete mode 100644 src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs delete mode 100644 src/WixToolset.Core/PreprocessContext.cs delete mode 100644 src/WixToolset.Core/PreprocessResult.cs delete mode 100644 src/WixToolset.Core/Preprocessor.cs delete mode 100644 src/WixToolset.Core/Properties/AssemblyInfo.cs delete mode 100644 src/WixToolset.Core/ResolveContext.cs delete mode 100644 src/WixToolset.Core/ResolveFileResult.cs delete mode 100644 src/WixToolset.Core/ResolveResult.cs delete mode 100644 src/WixToolset.Core/ResolvedCabinet.cs delete mode 100644 src/WixToolset.Core/Resolver.cs delete mode 100644 src/WixToolset.Core/SourceFile.cs delete mode 100644 src/WixToolset.Core/UnbindContext.cs delete mode 100644 src/WixToolset.Core/Unbinder.cs delete mode 100644 src/WixToolset.Core/VariableResolution.cs delete mode 100644 src/WixToolset.Core/VariableResolver.cs delete mode 100644 src/WixToolset.Core/WixToolset.Core.csproj delete mode 100644 src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject delete mode 100644 src/WixToolset.Core/WixToolsetServiceProvider.cs delete mode 100644 src/WixToolset.Core/WixToolsetServiceProviderFactory.cs delete mode 100644 src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj delete mode 100644 src/test/CompileCoreTestExtensionWixlib/Program.cs delete mode 100644 src/test/Example.Extension/Data/example.txt delete mode 100644 src/test/Example.Extension/Data/example.wxs delete mode 100644 src/test/Example.Extension/Example.Extension.csproj delete mode 100644 src/test/Example.Extension/ExampleCompilerExtension.cs delete mode 100644 src/test/Example.Extension/ExampleExtensionData.cs delete mode 100644 src/test/Example.Extension/ExampleExtensionFactory.cs delete mode 100644 src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs delete mode 100644 src/test/Example.Extension/ExampleRow.cs delete mode 100644 src/test/Example.Extension/ExampleSearchSymbol.cs delete mode 100644 src/test/Example.Extension/ExampleSymbol.cs delete mode 100644 src/test/Example.Extension/ExampleSymbolDefinitions.cs delete mode 100644 src/test/Example.Extension/ExampleTableDefinitions.cs delete mode 100644 src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs delete mode 100644 src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs delete mode 100644 src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CabFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs create mode 100644 src/version.json create mode 100644 src/wix/Custom.Build.props create mode 100644 src/wix/Directory.Build.props create mode 100644 src/wix/Directory.Build.targets create mode 100644 src/wix/Directory.csproj.props create mode 100644 src/wix/Directory.csproj.targets create mode 100644 src/wix/README.md create mode 100644 src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/BundleBackend.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs create mode 100644 src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs create mode 100644 src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs create mode 100644 src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs create mode 100644 src/wix/WixToolset.Core.Burn/ISearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs create mode 100644 src/wix/WixToolset.Core.Burn/RowIndexedList.cs create mode 100644 src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj create mode 100644 src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj create mode 100644 src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/WixRunner.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj create mode 100644 src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Differ.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Melter.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs create mode 100644 src/wix/WixToolset.Core.sln create mode 100644 src/wix/WixToolset.Core.v3.ncrunchsolution create mode 100644 src/wix/WixToolset.Core/Bind/DelayedField.cs create mode 100644 src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs create mode 100644 src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs create mode 100644 src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs create mode 100644 src/wix/WixToolset.Core/Bind/FileResolver.cs create mode 100644 src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs create mode 100644 src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs create mode 100644 src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs create mode 100644 src/wix/WixToolset.Core/BindContext.cs create mode 100644 src/wix/WixToolset.Core/BindFileWithPath.cs create mode 100644 src/wix/WixToolset.Core/BindPath.cs create mode 100644 src/wix/WixToolset.Core/BindResult.cs create mode 100644 src/wix/WixToolset.Core/Binder.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/BuildCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLine.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CompileCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/HelpCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/VersionCommand.cs create mode 100644 src/wix/WixToolset.Core/Common.cs create mode 100644 src/wix/WixToolset.Core/Compile/CompilerPayload.cs create mode 100644 src/wix/WixToolset.Core/CompileContext.cs create mode 100644 src/wix/WixToolset.Core/Compiler.cs create mode 100644 src/wix/WixToolset.Core/CompilerCore.cs create mode 100644 src/wix/WixToolset.Core/CompilerErrors.cs create mode 100644 src/wix/WixToolset.Core/CompilerWarnings.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Bundle.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Dependency.cs create mode 100644 src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Module.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Package.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Patch.cs create mode 100644 src/wix/WixToolset.Core/Compiler_PatchCreation.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Tag.cs create mode 100644 src/wix/WixToolset.Core/Compiler_UI.cs create mode 100644 src/wix/WixToolset.Core/ComponentKeyPath.cs create mode 100644 src/wix/WixToolset.Core/DecompileContext.cs create mode 100644 src/wix/WixToolset.Core/DecompileResult.cs create mode 100644 src/wix/WixToolset.Core/Decompiler.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs create mode 100644 src/wix/WixToolset.Core/IBinder.cs create mode 100644 src/wix/WixToolset.Core/ICompiler.cs create mode 100644 src/wix/WixToolset.Core/IDecompiler.cs create mode 100644 src/wix/WixToolset.Core/ILayoutCreator.cs create mode 100644 src/wix/WixToolset.Core/ILibrarian.cs create mode 100644 src/wix/WixToolset.Core/ILinker.cs create mode 100644 src/wix/WixToolset.Core/ILocalizationParser.cs create mode 100644 src/wix/WixToolset.Core/IPreprocessor.cs create mode 100644 src/wix/WixToolset.Core/IResolver.cs create mode 100644 src/wix/WixToolset.Core/IUnbinder.cs create mode 100644 src/wix/WixToolset.Core/IncludedFile.cs create mode 100644 src/wix/WixToolset.Core/IncribeContext.cs create mode 100644 src/wix/WixToolset.Core/LayoutContext.cs create mode 100644 src/wix/WixToolset.Core/LayoutCreator.cs create mode 100644 src/wix/WixToolset.Core/Librarian.cs create mode 100644 src/wix/WixToolset.Core/LibraryContext.cs create mode 100644 src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToFeature.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToModule.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs create mode 100644 src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs create mode 100644 src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/SymbolWithSection.cs create mode 100644 src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs create mode 100644 src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs create mode 100644 src/wix/WixToolset.Core/LinkContext.cs create mode 100644 src/wix/WixToolset.Core/Linker.cs create mode 100644 src/wix/WixToolset.Core/LinkerErrors.cs create mode 100644 src/wix/WixToolset.Core/LinkerWarnings.cs create mode 100644 src/wix/WixToolset.Core/LocalizationParser.cs create mode 100644 src/wix/WixToolset.Core/ParsedWixVariable.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IfContext.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IfState.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs create mode 100644 src/wix/WixToolset.Core/PreprocessContext.cs create mode 100644 src/wix/WixToolset.Core/PreprocessResult.cs create mode 100644 src/wix/WixToolset.Core/Preprocessor.cs create mode 100644 src/wix/WixToolset.Core/Properties/AssemblyInfo.cs create mode 100644 src/wix/WixToolset.Core/ResolveContext.cs create mode 100644 src/wix/WixToolset.Core/ResolveFileResult.cs create mode 100644 src/wix/WixToolset.Core/ResolveResult.cs create mode 100644 src/wix/WixToolset.Core/ResolvedCabinet.cs create mode 100644 src/wix/WixToolset.Core/Resolver.cs create mode 100644 src/wix/WixToolset.Core/SourceFile.cs create mode 100644 src/wix/WixToolset.Core/UnbindContext.cs create mode 100644 src/wix/WixToolset.Core/Unbinder.cs create mode 100644 src/wix/WixToolset.Core/VariableResolution.cs create mode 100644 src/wix/WixToolset.Core/VariableResolver.cs create mode 100644 src/wix/WixToolset.Core/WixToolset.Core.csproj create mode 100644 src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject create mode 100644 src/wix/WixToolset.Core/WixToolsetServiceProvider.cs create mode 100644 src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs create mode 100644 src/wix/appveyor.cmd create mode 100644 src/wix/appveyor.yml create mode 100644 src/wix/nuget.config create mode 100644 src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj create mode 100644 src/wix/test/CompileCoreTestExtensionWixlib/Program.cs create mode 100644 src/wix/test/Example.Extension/Data/example.txt create mode 100644 src/wix/test/Example.Extension/Data/example.wxs create mode 100644 src/wix/test/Example.Extension/Example.Extension.csproj create mode 100644 src/wix/test/Example.Extension/ExampleCompilerExtension.cs create mode 100644 src/wix/test/Example.Extension/ExampleExtensionData.cs create mode 100644 src/wix/test/Example.Extension/ExampleExtensionFactory.cs create mode 100644 src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs create mode 100644 src/wix/test/Example.Extension/ExampleRow.cs create mode 100644 src/wix/test/Example.Extension/ExampleSearchSymbol.cs create mode 100644 src/wix/test/Example.Extension/ExampleSymbol.cs create mode 100644 src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs create mode 100644 src/wix/test/Example.Extension/ExampleTableDefinitions.cs create mode 100644 src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs create mode 100644 src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs create mode 100644 src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs delete mode 100644 version.json diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 1d72e683..00000000 --- a/.editorconfig +++ /dev/null @@ -1,37 +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. -# -# Do NOT modify this file. Update the canonical version in Home\repo-template\src\.editorconfig -# then update all of the repos. - -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true - -[*.{cs,vb}] -dotnet_sort_system_directives_first = true - -[*.cs] -csharp_indent_case_contents = true : error -csharp_indent_switch_labels = true : error -csharp_new_line_before_open_brace = all -csharp_prefer_braces = true : error -csharp_style_expression_bodied_methods = when_on_single_line : suggestion -csharp_style_expression_bodied_constructors = when_on_single_line : suggestion -csharp_style_expression_bodied_operators = when_on_single_line : suggestion -csharp_style_expression_bodied_properties = when_on_single_line : suggestion -csharp_style_expression_bodied_indexers = when_on_single_line : suggestion -csharp_style_expression_bodied_accessors = when_on_single_line : suggestion -csharp_style_var_elsewhere = true : suggestion -csharp_style_var_for_built_in_types = true : suggestion -csharp_style_var_when_type_is_apparent = true : suggestion -dotnet_style_qualification_for_event = true : error -dotnet_style_qualification_for_field = true : error -dotnet_style_qualification_for_method = true : error -dotnet_style_qualification_for_property = true : error - -[*.targets] -indent_size = 2 diff --git a/README.md b/README.md deleted file mode 100644 index 622cd3f9..00000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Core -WixToolset.Core - preprocessor, compiler, linker and binder for Windows Installer and Burn - diff --git a/WixToolset.Core.sln b/WixToolset.Core.sln deleted file mode 100644 index 523c960e..00000000 --- a/WixToolset.Core.sln +++ /dev/null @@ -1,156 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 -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 -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 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.ExtensionCache", "src\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj", "{A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1284331E-BC6C-426D-AAAF-140C0174F875}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Extension", "src\test\Example.Extension\Example.Extension.csproj", "{C66C2503-C671-4230-8B48-1D93A8532A28}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegration", "src\test\WixToolsetTest.CoreIntegration\WixToolsetTest.CoreIntegration.csproj", "{E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Burn", "src\test\WixToolsetTest.Core.Burn\WixToolsetTest.Core.Burn.csproj", "{DF63F589-028E-45A1-A212-948FACF1FDCD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}" -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 - {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 - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.ActiveCfg = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.Build.0 = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.ActiveCfg = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.Build.0 = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.Build.0 = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.ActiveCfg = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.Build.0 = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.ActiveCfg = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.Build.0 = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.ActiveCfg = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.Build.0 = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.ActiveCfg = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.Build.0 = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.Build.0 = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.ActiveCfg = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.Build.0 = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.ActiveCfg = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.Build.0 = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.Build.0 = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.Build.0 = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.Build.0 = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.ActiveCfg = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.Build.0 = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.ActiveCfg = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.Build.0 = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.ActiveCfg = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.Build.0 = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.ActiveCfg = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.Build.0 = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.Build.0 = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.ActiveCfg = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.Build.0 = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.ActiveCfg = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.Build.0 = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.ActiveCfg = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.Build.0 = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.ActiveCfg = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.Build.0 = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.Build.0 = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.ActiveCfg = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875} - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875} - {DF63F589-028E-45A1-A212-948FACF1FDCD} = {1284331E-BC6C-426D-AAAF-140C0174F875} - {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA} - EndGlobalSection -EndGlobal diff --git a/WixToolset.Core.v3.ncrunchsolution b/WixToolset.Core.v3.ncrunchsolution deleted file mode 100644 index 10420ac9..00000000 --- a/WixToolset.Core.v3.ncrunchsolution +++ /dev/null @@ -1,6 +0,0 @@ - - - True - True - - \ No newline at end of file diff --git a/appveyor.cmd b/appveyor.cmd deleted file mode 100644 index 02db695b..00000000 --- a/appveyor.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@setlocal -@pushd %~dp0 -@set _P=%~dp0build\Release\publish -@set _C=Release -@if /i "%1"=="debug" set _C=Debug - -:: Restore -msbuild -p:Configuration=%_C% -t:Restore || exit /b - -:: Build -msbuild -p:Configuration=%_C% || exit /b - -:: Test -dotnet test -c %_C% --no-build || exit /b - -:: Pack -msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack || exit /b - -@popd -@endlocal diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 364569cf..00000000 --- a/appveyor.yml +++ /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. -# -# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml -# then update all of the repos. - -branches: - only: - - master - - develop - -image: Visual Studio 2019 - -version: 0.0.0.{build} -configuration: Release - -environment: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - NUGET_XMLDOC_MODE: skip - -build_script: - - appveyor.cmd - -test: off - -pull_requests: - do_not_increment_build_number: true - -nuget: - disable_publish_on_pr: true - -skip_branch_with_pr: true -skip_tags: true - -artifacts: -- path: build\Release\**\*.nupkg - name: nuget -- path: build\Release\**\*.snupkg - name: snupkg - -notifications: -- provider: Slack - incoming_webhook: - secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA= diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 022f9240..00000000 --- a/nuget.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 00000000..1d72e683 --- /dev/null +++ b/src/.editorconfig @@ -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. +# +# Do NOT modify this file. Update the canonical version in Home\repo-template\src\.editorconfig +# then update all of the repos. + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.{cs,vb}] +dotnet_sort_system_directives_first = true + +[*.cs] +csharp_indent_case_contents = true : error +csharp_indent_switch_labels = true : error +csharp_new_line_before_open_brace = all +csharp_prefer_braces = true : error +csharp_style_expression_bodied_methods = when_on_single_line : suggestion +csharp_style_expression_bodied_constructors = when_on_single_line : suggestion +csharp_style_expression_bodied_operators = when_on_single_line : suggestion +csharp_style_expression_bodied_properties = when_on_single_line : suggestion +csharp_style_expression_bodied_indexers = when_on_single_line : suggestion +csharp_style_expression_bodied_accessors = when_on_single_line : suggestion +csharp_style_var_elsewhere = true : suggestion +csharp_style_var_for_built_in_types = true : suggestion +csharp_style_var_when_type_is_apparent = true : suggestion +dotnet_style_qualification_for_event = true : error +dotnet_style_qualification_for_field = true : error +dotnet_style_qualification_for_method = true : error +dotnet_style_qualification_for_property = true : error + +[*.targets] +indent_size = 2 diff --git a/src/Custom.Build.props b/src/Custom.Build.props deleted file mode 100644 index 889fb62e..00000000 --- a/src/Custom.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - diff --git a/src/Directory.Build.props b/src/Directory.Build.props deleted file mode 100644 index b3c6287c..00000000 --- a/src/Directory.Build.props +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Debug - false - MSB3246 - - $(MSBuildProjectName) - $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\)) - $(BaseOutputPath)obj\$(ProjectName)\ - $(BaseOutputPath)$(Configuration)\ - - WiX Toolset Team - WiX Toolset - Copyright (c) .NET Foundation and contributors. All rights reserved. - MS-RL - WiX Toolset - - - - - diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets deleted file mode 100644 index 2fcc765a..00000000 --- a/src/Directory.Build.targets +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - true - $(SolutionPath) - $(NCrunchOriginalSolutionPath) - - - - - - - $([System.IO.File]::ReadAllText($(TheSolutionPath))) - $([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) )) - (?<="[PackageName]", ")(.*)(?=", ") - - - - - - %(Identity) - $(SolutionFileContent.Contains('\%(Identity).csproj')) - - - - - $(RegexPattern.Replace('[PackageName]','%(PackageName)') ) - $([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)')) - - - - - - - - - - - - - - diff --git a/src/Directory.csproj.props b/src/Directory.csproj.props deleted file mode 100644 index 81d24ad1..00000000 --- a/src/Directory.csproj.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - - true - true - $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk)) - false - - diff --git a/src/Directory.csproj.targets b/src/Directory.csproj.targets deleted file mode 100644 index c3270426..00000000 --- a/src/Directory.csproj.targets +++ /dev/null @@ -1,26 +0,0 @@ - - - - - false - $(OutputPath)\$(AssemblyName).xml - - - - - $(PrivateRepositoryUrl.Replace('.git','')) - - $(MSBuildProjectName).nuspec - $(OutputPath)..\ - $(NuspecProperties);Id=$(PackageId);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description);Title=$(Title) - $(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl) - true - snupkg - - - - diff --git a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs deleted file mode 100644 index 0da78797..00000000 --- a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System; - using System.Xml; - using WixToolset.Data.Symbols; - - internal abstract class BaseSearchFacade : ISearchFacade - { - protected WixSearchSymbol SearchSymbol { get; set; } - - public virtual void WriteXml(XmlTextWriter writer) - { - writer.WriteAttributeString("Id", this.SearchSymbol.Id.Id); - writer.WriteAttributeString("Variable", this.SearchSymbol.Variable); - if (!String.IsNullOrEmpty(this.SearchSymbol.Condition)) - { - writer.WriteAttributeString("Condition", this.SearchSymbol.Condition); - } - if (!String.IsNullOrEmpty(this.SearchSymbol.BundleExtensionRef)) - { - writer.WriteAttributeString("ExtensionId", this.SearchSymbol.BundleExtensionRef); - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs deleted file mode 100644 index 4a4f06f3..00000000 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ /dev/null @@ -1,650 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Burn.Bind; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Binds a this.bundle. - /// - internal class BindBundleCommand - { - public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) - { - this.ServiceProvider = context.ServiceProvider; - - this.Messaging = context.ServiceProvider.GetService(); - - this.BackendHelper = context.ServiceProvider.GetService(); - this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); - this.PayloadHarvester = context.ServiceProvider.GetService(); - - this.DefaultCompressionLevel = context.DefaultCompressionLevel; - this.DelayedFields = context.DelayedFields; - this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; - this.IntermediateFolder = context.IntermediateFolder; - this.Output = context.IntermediateRepresentation; - this.OutputPath = context.OutputPath; - this.OutputPdbPath = context.PdbPath; - //this.VariableResolver = context.VariableResolver; - - this.BackendExtensions = backedExtensions; - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } - - private IPayloadHarvester PayloadHarvester { get; } - - private CompressionLevel? DefaultCompressionLevel { get; } - - public IEnumerable DelayedFields { get; } - - public IEnumerable ExpectedEmbeddedFiles { get; } - - private IEnumerable BackendExtensions { get; } - - private Intermediate Output { get; } - - private string OutputPath { get; } - - private string OutputPdbPath { get; } - - private string IntermediateFolder { get; } - - private IVariableResolver VariableResolver { get; } - - public IReadOnlyCollection FileTransfers { get; private set; } - - public IReadOnlyCollection TrackedFiles { get; private set; } - - public WixOutput Wixout { get; private set; } - - public void Execute() - { - var section = this.Output.Sections.Single(); - - var fileTransfers = new List(); - var trackedFiles = new List(); - - // 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! - var chainPackageSymbols = this.GetRequiredSymbols(); - - var wixGroupSymbols = this.GetRequiredSymbols(); - - // Ensure there is one and only one WixBundleSymbol. - // The compiler and linker behavior should have colluded to get - // this behavior. - var bundleSymbol = this.GetSingleSymbol(); - - bundleSymbol.ProviderKey = bundleSymbol.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant(); - - bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. - - // Ensure there is one and only one WixBootstrapperApplicationDllSymbol. - // The compiler and linker behavior should have colluded to get - // this behavior. - var bundleApplicationDllSymbol = this.GetSingleSymbol(); - - // Ensure there is one and only one WixChainSymbol. - // The compiler and linker behavior should have colluded to get - // this behavior. - var chainSymbol = this.GetSingleSymbol(); - - if (this.Messaging.EncounteredError) - { - return; - } - - // If there are any fields to resolve later, create the cache to populate during bind. - var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; - - IEnumerable orderedSearches; - IDictionary> extensionSearchSymbolsById; - { - var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); - orderSearchesCommand.Execute(); - - orderedSearches = orderSearchesCommand.OrderedSearchFacades; - extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; - } - - // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). - { - var extractedFiles = this.BackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); - - trackedFiles.AddRange(extractedFiles); - } - - // Get the explicit payloads. - var payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); - - var layoutDirectory = Path.GetDirectoryName(this.OutputPath); - - // Process the explicitly authored payloads. - ISet processedPayloads; - { - var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - - processedPayloads = new HashSet(payloadSymbols.Keys); - } - - IDictionary facades; - { - var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section); - command.Execute(); - - facades = command.PackageFacades; - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Process each package facade. Note this is likely to add payloads and other symbols so - // note that any indexes created above may be out of date now. - foreach (var facade in facades.Values) - { - switch (facade.PackageSymbol.Type) - { - case WixBundlePackageType.Exe: - { - var command = new ProcessExePackageCommand(facade, payloadSymbols); - command.Execute(); - } - break; - - case WixBundlePackageType.Msi: - { - var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]); - command.Execute(); - } - break; - - case WixBundlePackageType.Msp: - { - var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadSymbols); - command.Execute(); - } - break; - - case WixBundlePackageType.Msu: - { - var command = new ProcessMsuPackageCommand(facade, payloadSymbols); - command.Execute(); - } - break; - } - - if (null != variableCache) - { - BindBundleCommand.PopulatePackageVariableCache(facade, variableCache); - } - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) - // are present. - payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - wixGroupSymbols = this.GetRequiredSymbols(); - packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); - - // Process the payloads that were added by processing the packages. - { - var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); - - var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - - processedPayloads = null; - } - - // Set the package metadata from the payloads now that we have the complete payload information. - { - foreach (var facade in facades.Values) - { - facade.PackageSymbol.Size = 0; - - var packagePayloads = packagesPayloads[facade.PackageId]; - - foreach (var payload in packagePayloads.Values) - { - facade.PackageSymbol.Size += payload.FileSize.Value; - } - - if (!facade.PackageSymbol.InstallSize.HasValue) - { - facade.PackageSymbol.InstallSize = facade.PackageSymbol.Size; - } - - var packagePayload = payloadSymbols[facade.PackageSymbol.PayloadRef]; - - if (String.IsNullOrEmpty(facade.PackageSymbol.Description)) - { - facade.PackageSymbol.Description = packagePayload.Description; - } - - if (String.IsNullOrEmpty(facade.PackageSymbol.DisplayName)) - { - facade.PackageSymbol.DisplayName = packagePayload.DisplayName; - } - } - } - - // Give the UX payloads their embedded IDs... - var uxPayloadIndex = 0; - { - foreach (var payload in payloadSymbols.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef)) - { - // 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) - { - this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path)); - 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(ErrorMessages.MissingBundleInformation("BootstrapperApplication")); - } - - // Give the embedded payloads without an embedded id yet an embedded id. - var payloadIndex = 0; - foreach (var payload in payloadSymbols.Values) - { - Debug.Assert(PackagingType.Unknown != payload.Packaging); - - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) - { - payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex); - ++payloadIndex; - } - } - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Determine patches to automatically slipstream. - { - var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - IEnumerable orderedFacades; - IEnumerable boundaries; - { - var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, section, facades); - command.Execute(); - - orderedFacades = command.OrderedPackageFacades; - boundaries = command.UsedRollbackBoundaries; - } - - // Resolve any delayed fields before generating the manifest. - if (this.DelayedFields.Any()) - { - this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); - } - - { - var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); - command.Execute(); - - if (!String.IsNullOrEmpty(command.BundleProviderKey)) - { - bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. - } - } - - // Update the bundle per-machine/per-user scope based on the chained packages. - this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); - - var softwareTags = section.Symbols.OfType().ToList(); - if (softwareTags.Any()) - { - var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); - command.Execute(); - } - - this.DetectDuplicateCacheIds(facades); - - if (this.Messaging.EncounteredError) - { - return; - } - - // Give the extension one last hook before generating the output files. - foreach (var extension in this.BackendExtensions) - { - extension.SymbolsFinalized(section); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Generate data for all manifests. - { - var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Generate the core-defined BA manifest tables... - string baManifestPath; - { - var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper); - command.Execute(); - - var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; - baManifestPath = command.OutputPath; - payloadSymbols.Add(baManifestPayload.Id.Id, baManifestPayload); - ++uxPayloadIndex; - - trackedFiles.Add(this.BackendHelper.TrackFile(baManifestPath, TrackedFileType.Temporary)); - } - - // Generate the bundle extension manifest... - string bextManifestPath; - { - var command = new CreateBundleExtensionManifestCommand(section, bundleSymbol, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper); - command.Execute(); - - var bextManifestPayload = command.BundleExtensionManifestPayloadRow; - bextManifestPath = command.OutputPath; - payloadSymbols.Add(bextManifestPayload.Id.Id, bextManifestPayload); - ++uxPayloadIndex; - - trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary)); - } - - var containers = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - { - var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // 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. - WixBundleContainerSymbol uxContainer; - IEnumerable uxPayloads; - { - var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - - uxContainer = command.UXContainer; - uxPayloads = command.UXContainerPayloads; - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Resolve the download URLs now that we have all of the containers and payloads calculated. - { - var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols); - command.Execute(); - } - - // Create the bundle manifest. - string manifestPath; - { - var executableName = Path.GetFileName(this.OutputPath); - - var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); - command.Execute(); - - manifestPath = command.OutputPath; - trackedFiles.Add(this.BackendHelper.TrackFile(manifestPath, TrackedFileType.Temporary)); - } - - // Create the UX container. - { - var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel); - command.Execute(); - - uxContainer.Hash = command.Hash; - uxContainer.Size = command.Size; - - trackedFiles.Add(this.BackendHelper.TrackFile(uxContainer.WorkingPath, TrackedFileType.Temporary)); - } - - { - var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); - command.Execute(); - - fileTransfers.Add(command.Transfer); - trackedFiles.Add(this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final)); - } - -#if TODO // does this need to come back, or do they only need to be in TrackedFiles? - this.ContentFilePaths = payloadSymbols.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); -#endif - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - this.Wixout = this.CreateWixout(trackedFiles, this.Output, manifestPath, baManifestPath, bextManifestPath); - } - - private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, string manifestPath, string baDataPath, string bextDataPath) - { - WixOutput wixout; - - if (String.IsNullOrEmpty(this.OutputPdbPath)) - { - wixout = WixOutput.Create(); - } - else - { - var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); - trackedFiles.Add(trackPdb); - - wixout = WixOutput.Create(trackPdb.Path); - } - - intermediate.Save(wixout); - - wixout.ImportDataStream(BurnConstants.BurnManifestWixOutputStreamName, manifestPath); - wixout.ImportDataStream(BurnConstants.BootstrapperApplicationDataWixOutputStreamName, baDataPath); - wixout.ImportDataStream(BurnConstants.BundleExtensionDataWixOutputStreamName, bextDataPath); - - wixout.Reopen(); - - return wixout; - } - - /// - /// Populates the variable cache with specific package properties. - /// - /// The package facade with properties to cache. - /// The property cache. - private static void PopulatePackageVariableCache(PackageFacade facade, IDictionary variableCache) - { - var package = facade.PackageSymbol; - var id = package.Id.Id; - - variableCache.Add(String.Concat("packageDescription.", id), package.Description ?? String.Empty); - variableCache.Add(String.Concat("packageName.", id), package.DisplayName ?? String.Empty); - variableCache.Add(String.Concat("packageVersion.", id), package.Version); - - if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - variableCache.Add(String.Concat("packageLanguage.", id), msiPackage.ProductLanguage.ToString()); - variableCache.Add(String.Concat("packageManufacturer.", id), msiPackage.Manufacturer ?? String.Empty); - } - else - { - variableCache.Add(String.Concat("packageLanguage.", id), String.Empty); - variableCache.Add(String.Concat("packageManufacturer.", id), String.Empty); - } - } - - private void ResolveBundleInstallScope(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable facades) - { - var dependencySymbolsById = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - foreach (var facade in facades) - { - if (bundleSymbol.PerMachine && YesNoDefaultType.No == facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); - - bundleSymbol.Attributes &= ~WixBundleAttributes.PerMachine; - break; - } - } - - foreach (var facade in facades) - { - // Update package scope from bundle scope if default. - if (YesNoDefaultType.Default == facade.PackageSymbol.PerMachine) - { - facade.PackageSymbol.PerMachine = bundleSymbol.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 (!bundleSymbol.PerMachine && - YesNoDefaultType.Yes == facade.PackageSymbol.PerMachine && - !facade.PackageSymbol.Permanent && - dependencySymbolsById.ContainsKey(facade.PackageId)) - { - this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); - } - } - } - - private void DetectDuplicateCacheIds(IDictionary facades) - { - var duplicateCacheIdDetector = new Dictionary(); - - foreach (var facade in facades.Values) - { - if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage)) - { - this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId, facade.PackageId)); - this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(collisionPackage.SourceLineNumbers)); - } - else - { - duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol); - } - } - } - - private IEnumerable GetRequiredSymbols() where T : IntermediateSymbol - { - var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); - - if (0 == symbols.Count) - { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); - } - - return symbols; - } - - private T GetSingleSymbol() where T : IntermediateSymbol - { - var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); - - if (1 != symbols.Count) - { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); - } - - return symbols[0]; - } - - private static Dictionary> RecalculatePackagesPayloads(Dictionary payloadSymbols, IEnumerable wixGroupSymbols) - { - var packagesPayloads = new Dictionary>(); - - foreach (var groupSymbol in wixGroupSymbols) - { - if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) - { - var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; - - if (ComplexReferenceParentType.Package == groupSymbol.ParentType) - { - if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById)) - { - packagePayloadsById = new Dictionary(); - packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById); - } - - packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol); - } - } - } - - return packagesPayloads; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs deleted file mode 100644 index 773250d7..00000000 --- a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System.Xml; - using WixToolset.Data.Symbols; - - internal class ExtensionSearchFacade : BaseSearchFacade - { - public ExtensionSearchFacade(WixSearchSymbol searchSymbol) - { - this.SearchSymbol = searchSymbol; - } - - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("ExtensionSearch"); - - base.WriteXml(writer); - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs deleted file mode 100644 index a76f84ec..00000000 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.ExtensibilityServices; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class GenerateManifestDataFromIRCommand - { - public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions, IBurnBackendHelper backendHelper, IDictionary> extensionSearchSymbolsById) - { - this.Messaging = messaging; - this.Section = section; - this.BackendExtensions = backendExtensions; - this.BackendHelper = backendHelper; - this.ExtensionSearchSymbolsById = extensionSearchSymbolsById; - } - - private IEnumerable BackendExtensions { get; } - - private IBurnBackendHelper BackendHelper { get; } - - private IDictionary> ExtensionSearchSymbolsById { get; } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public void Execute() - { - var symbols = this.Section.Symbols.ToList(); - var cellsByCustomDataAndElementId = new Dictionary>(); - var customDataById = new Dictionary(); - - foreach (var kvp in this.ExtensionSearchSymbolsById) - { - var extensionId = kvp.Key; - var extensionSearchSymbols = kvp.Value; - foreach (var extensionSearchSymbol in extensionSearchSymbols) - { - this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchSymbol, symbolIdIsIdAttribute: true); - symbols.Remove(extensionSearchSymbol); - } - } - - foreach (var symbol in symbols) - { - var unknownSymbol = false; - switch (symbol.Definition.Type) - { - // Symbols used internally and are not added to a data manifest. - case SymbolDefinitionType.ProvidesDependency: - case SymbolDefinitionType.WixApprovedExeForElevation: - case SymbolDefinitionType.WixBootstrapperApplication: - case SymbolDefinitionType.WixBootstrapperApplicationDll: - case SymbolDefinitionType.WixBundle: - case SymbolDefinitionType.WixBundleContainer: - case SymbolDefinitionType.WixBundleCustomDataAttribute: - case SymbolDefinitionType.WixBundleExePackage: - case SymbolDefinitionType.WixBundleExePackagePayload: - case SymbolDefinitionType.WixBundleExtension: - case SymbolDefinitionType.WixBundleMsiFeature: - case SymbolDefinitionType.WixBundleMsiPackage: - case SymbolDefinitionType.WixBundleMsiPackagePayload: - case SymbolDefinitionType.WixBundleMsiProperty: - case SymbolDefinitionType.WixBundleMspPackage: - case SymbolDefinitionType.WixBundleMspPackagePayload: - case SymbolDefinitionType.WixBundleMsuPackage: - case SymbolDefinitionType.WixBundleMsuPackagePayload: - case SymbolDefinitionType.WixBundlePackage: - case SymbolDefinitionType.WixBundlePackageCommandLine: - case SymbolDefinitionType.WixBundlePackageExitCode: - case SymbolDefinitionType.WixBundlePackageGroup: - case SymbolDefinitionType.WixBundlePatchTargetCode: - case SymbolDefinitionType.WixBundlePayload: - case SymbolDefinitionType.WixBundlePayloadGroup: - case SymbolDefinitionType.WixBundleRelatedPackage: - case SymbolDefinitionType.WixBundleRollbackBoundary: - case SymbolDefinitionType.WixBundleSlipstreamMsp: - case SymbolDefinitionType.WixBundleTag: - case SymbolDefinitionType.WixBundleUpdate: - case SymbolDefinitionType.WixBundleVariable: - case SymbolDefinitionType.WixBuildInfo: - case SymbolDefinitionType.WixChain: - case SymbolDefinitionType.WixComponentSearch: - case SymbolDefinitionType.WixDependencyProvider: - case SymbolDefinitionType.WixFileSearch: - case SymbolDefinitionType.WixGroup: - case SymbolDefinitionType.WixProductSearch: - case SymbolDefinitionType.WixRegistrySearch: - case SymbolDefinitionType.WixRelatedBundle: - case SymbolDefinitionType.WixSearch: - case SymbolDefinitionType.WixSearchRelation: - case SymbolDefinitionType.WixSetVariable: - case SymbolDefinitionType.WixUpdateRegistration: - break; - - // Symbols used before binding. - case SymbolDefinitionType.WixComplexReference: - case SymbolDefinitionType.WixOrdering: - case SymbolDefinitionType.WixSimpleReference: - case SymbolDefinitionType.WixVariable: - break; - - // Symbols to investigate: - case SymbolDefinitionType.WixChainItem: - break; - - case SymbolDefinitionType.WixBundleCustomData: - unknownSymbol = !this.IndexBundleCustomDataSymbol((WixBundleCustomDataSymbol)symbol, customDataById); - break; - - case SymbolDefinitionType.WixBundleCustomDataCell: - this.IndexBundleCustomDataCellSymbol((WixBundleCustomDataCellSymbol)symbol, cellsByCustomDataAndElementId); - break; - - case SymbolDefinitionType.MustBeFromAnExtension: - unknownSymbol = !this.AddSymbolFromExtension(symbol); - break; - - default: - unknownSymbol = true; - break; - } - - if (unknownSymbol) - { - this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); - } - } - - this.AddIndexedCellSymbols(customDataById, cellsByCustomDataAndElementId); - } - - private bool IndexBundleCustomDataSymbol(WixBundleCustomDataSymbol wixBundleCustomDataSymbol, Dictionary customDataById) - { - switch (wixBundleCustomDataSymbol.Type) - { - case WixBundleCustomDataType.BootstrapperApplication: - case WixBundleCustomDataType.BundleExtension: - break; - default: - return false; - } - - var customDataId = wixBundleCustomDataSymbol.Id.Id; - customDataById.Add(customDataId, wixBundleCustomDataSymbol); - return true; - } - - private void IndexBundleCustomDataCellSymbol(WixBundleCustomDataCellSymbol wixBundleCustomDataCellSymbol, Dictionary> cellsByCustomDataAndElementId) - { - var tableAndRowId = wixBundleCustomDataCellSymbol.CustomDataRef + "/" + wixBundleCustomDataCellSymbol.ElementId; - if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells)) - { - cells = new List(); - cellsByCustomDataAndElementId.Add(tableAndRowId, cells); - } - - cells.Add(wixBundleCustomDataCellSymbol); - } - - private void AddIndexedCellSymbols(Dictionary customDataById, Dictionary> cellsByCustomDataAndElementId) - { - foreach (var elementValues in cellsByCustomDataAndElementId.Values) - { - var elementName = elementValues[0].CustomDataRef; - var customDataSymbol = customDataById[elementName]; - - var attributeNames = customDataSymbol.AttributeNamesSeparated; - - var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value); - - var sb = new StringBuilder(); - using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) - { - switch (customDataSymbol.Type) - { - case WixBundleCustomDataType.BootstrapperApplication: - writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); - break; - case WixBundleCustomDataType.BundleExtension: - writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); - break; - default: - throw new NotImplementedException(); - } - - // Write all row data as attributes in table column order. - foreach (var attributeName in attributeNames) - { - if (elementValuesByAttribute.TryGetValue(attributeName, out var value)) - { - writer.WriteAttributeString(attributeName, value); - } - } - - writer.WriteEndElement(); - } - - switch (customDataSymbol.Type) - { - case WixBundleCustomDataType.BootstrapperApplication: - this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); - break; - case WixBundleCustomDataType.BundleExtension: - this.BackendHelper.AddBundleExtensionData(customDataSymbol.BundleExtensionRef, sb.ToString()); - break; - default: - throw new NotImplementedException(); - } - } - } - - private bool AddSymbolFromExtension(IntermediateSymbol symbol) - { - foreach (var extension in this.BackendExtensions) - { - if (extension.TryProcessSymbol(this.Section, symbol)) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs deleted file mode 100644 index 24d6f542..00000000 --- a/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class LegacySearchFacade : BaseSearchFacade - { - public LegacySearchFacade(WixSearchSymbol searchSymbol, IntermediateSymbol searchSpecificSymbol) - { - this.SearchSymbol = searchSymbol; - this.SearchSpecificSymbol = searchSpecificSymbol; - } - - public IntermediateSymbol SearchSpecificSymbol { get; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup a search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - switch (this.SearchSpecificSymbol) - { - case WixComponentSearchSymbol symbol: - this.WriteComponentSearchXml(writer, symbol); - break; - case WixFileSearchSymbol symbol: - this.WriteFileSearchXml(writer, symbol); - break; - case WixProductSearchSymbol symbol: - this.WriteProductSearchXml(writer, symbol); - break; - case WixRegistrySearchSymbol symbol: - this.WriteRegistrySearchXml(writer, symbol); - break; - } - } - - private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchSymbol searchSymbol) - { - writer.WriteStartElement("MsiComponentSearch"); - - base.WriteXml(writer); - - writer.WriteAttributeString("ComponentId", searchSymbol.Guid); - - if (!String.IsNullOrEmpty(searchSymbol.ProductCode)) - { - writer.WriteAttributeString("ProductCode", searchSymbol.ProductCode); - } - - if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.KeyPath)) - { - writer.WriteAttributeString("Type", "keyPath"); - } - else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.WantDirectory)) - { - writer.WriteAttributeString("Type", "directory"); - } - - writer.WriteEndElement(); - } - - private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchSymbol searchSymbol) - { - writer.WriteStartElement((0 == (searchSymbol.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); - - base.WriteXml(writer); - - writer.WriteAttributeString("Path", searchSymbol.Path); - if (WixFileSearchAttributes.WantExists == (searchSymbol.Attributes & WixFileSearchAttributes.WantExists)) - { - writer.WriteAttributeString("Type", "exists"); - } - else if (WixFileSearchAttributes.WantVersion == (searchSymbol.Attributes & WixFileSearchAttributes.WantVersion)) - { - // Can never get here for DirectorySearch. - writer.WriteAttributeString("Type", "version"); - } - else - { - writer.WriteAttributeString("Type", "path"); - } - writer.WriteEndElement(); - } - - private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol symbol) - { - writer.WriteStartElement("MsiProductSearch"); - - base.WriteXml(writer); - - if (0 != (symbol.Attributes & WixProductSearchAttributes.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", symbol.Guid); - } - else - { - writer.WriteAttributeString("ProductCode", symbol.Guid); - } - - if (0 != (symbol.Attributes & WixProductSearchAttributes.Version)) - { - writer.WriteAttributeString("Type", "version"); - } - else if (0 != (symbol.Attributes & WixProductSearchAttributes.Language)) - { - writer.WriteAttributeString("Type", "language"); - } - else if (0 != (symbol.Attributes & WixProductSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (symbol.Attributes & WixProductSearchAttributes.Assignment)) - { - writer.WriteAttributeString("Type", "assignment"); - } - - writer.WriteEndElement(); - } - - private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchSymbol symbol) - { - writer.WriteStartElement("RegistrySearch"); - - base.WriteXml(writer); - - switch (symbol.Root) - { - case RegistryRootType.ClassesRoot: - writer.WriteAttributeString("Root", "HKCR"); - break; - case RegistryRootType.CurrentUser: - writer.WriteAttributeString("Root", "HKCU"); - break; - case RegistryRootType.LocalMachine: - writer.WriteAttributeString("Root", "HKLM"); - break; - case RegistryRootType.Users: - writer.WriteAttributeString("Root", "HKU"); - break; - } - - writer.WriteAttributeString("Key", symbol.Key); - - if (!String.IsNullOrEmpty(symbol.Value)) - { - writer.WriteAttributeString("Value", symbol.Value); - } - - var existenceOnly = 0 != (symbol.Attributes & WixRegistrySearchAttributes.WantExists); - - writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); - - if (0 != (symbol.Attributes & WixRegistrySearchAttributes.Win64)) - { - writer.WriteAttributeString("Win64", "yes"); - } - - if (!existenceOnly) - { - if (0 != (symbol.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/ProcessBundleSoftwareTagsCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs deleted file mode 100644 index f9ff23cb..00000000 --- a/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class ProcessBundleSoftwareTagsCommand - { - public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags) - { - this.Section = section; - this.SoftwareTags = softwareTags; - } - - private IntermediateSection Section { get; } - - private IEnumerable SoftwareTags { get; } - - public void Execute() - { - var bundleInfo = this.Section.Symbols.OfType().FirstOrDefault(); - var bundleId = NormalizeGuid(bundleInfo.BundleId); - var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); - - var uniqueId = String.Concat("wix:bundle/", bundleId); - var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); - - // Try to collect all the software id tags from all the child packages. - var containedTags = CollectPackageTags(this.Section); - - foreach (var bundleTag in this.SoftwareTags) - { - using (var ms = new MemoryStream()) - { - CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); - bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); - } - } - } - - private static string NormalizeGuid(string guidString) - { - if (Guid.TryParse(guidString, out var guid)) - { - return guid.ToString("D").ToUpperInvariant(); - } - - return guidString; - } - - private static IEnumerable CollectPackageTags(IntermediateSection section) - { - var tags = new List(); - - var msiPackages = section.Symbols.OfType().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); - if (msiPackages.Any()) - { - var payloadSymbolsById = section.Symbols.OfType().ToDictionary(s => s.Id.Id); - - foreach (var msiPackage in msiPackages) - { - var payload = payloadSymbolsById[msiPackage.PayloadRef]; - - using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) - { - using (var view = db.OpenExecuteView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) - { - foreach (var record in view.Records) - { - tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); - } - } - } - } - } - - return tags; - } - - private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable containedTags) - { - var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; - - using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) - { - writer.WriteStartDocument(); - writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); - writer.WriteAttributeString("tagId", uniqueId); - writer.WriteAttributeString("name", name); - writer.WriteAttributeString("version", version); - writer.WriteAttributeString("versionScheme", versionScheme); - - writer.WriteStartElement("Entity"); - writer.WriteAttributeString("name", manufacturer); - writer.WriteAttributeString("regid", regid); - writer.WriteAttributeString("role", "softwareCreator tagCreator"); - writer.WriteEndElement(); // - - if (!String.IsNullOrEmpty(persistendId)) - { - writer.WriteStartElement("Meta"); - writer.WriteAttributeString("persistentId", persistendId); - writer.WriteEndElement(); // - } - - foreach (var containedTag in containedTags) - { - writer.WriteStartElement("Link"); - writer.WriteAttributeString("rel", "component"); - writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); - writer.WriteEndElement(); // - } - - writer.WriteEndElement(); // - } - } - - private class SoftwareTag - { - public string Regid { get; set; } - - public string Id { get; set; } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs deleted file mode 100644 index 99effbc7..00000000 --- a/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.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.Core.Burn.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Extensibility.Services; - using WixToolset.Data.Symbols; - - internal class ProcessDependencyProvidersCommand - { - public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary facades) - { - this.Messaging = messaging; - this.Section = section; - - this.Facades = facades; - } - - public string BundleProviderKey { get; private set; } - - public Dictionary DependencySymbolsByKey { get; private set; } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IDictionary Facades { get; } - - /// - /// Sets the explicitly provided bundle provider key, if provided. And... - /// 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. - /// - public void Execute() - { - var dependencySymbols = this.Section.Symbols.OfType(); - - foreach (var dependency in dependencySymbols) - { - // Sets the provider key for the bundle, if it is not set already. - if (String.IsNullOrEmpty(this.BundleProviderKey)) - { - if (dependency.Bundle) - { - this.BundleProviderKey = dependency.ProviderKey; - } - } - - // Import any authored dependencies. These may merge with imported provides from MSI packages. - var packageId = dependency.ParentRef; - - if (this.Facades.TryGetValue(packageId, out var facade)) - { - if (String.IsNullOrEmpty(dependency.ProviderKey)) - { - switch (facade.SpecificPackageSymbol) - { - // The WixDependencyExtension allows an empty Key for MSIs and MSPs. - case WixBundleMsiPackageSymbol msiPackage: - dependency.ProviderKey = msiPackage.ProductCode; - break; - case WixBundleMspPackageSymbol mspPackage: - dependency.ProviderKey = mspPackage.PatchCode; - break; - } - } - - if (String.IsNullOrEmpty(dependency.Version)) - { - dependency.Version = facade.PackageSymbol.Version; - } - - // If the version is still missing, a version could not be gathered from the package and was not authored. - if (String.IsNullOrEmpty(dependency.Version)) - { - this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId)); - } - - if (String.IsNullOrEmpty(dependency.DisplayName)) - { - dependency.DisplayName = facade.PackageSymbol.DisplayName; - } - } - } - - this.DependencySymbolsByKey = this.GetDependencySymbolsByKey(dependencySymbols); - - // Generate providers for MSI and MSP packages that still do not have providers. - foreach (var facade in this.Facades.Values) - { - string key = null; - - if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - key = msiPackage.ProductCode; - } - else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) - { - key = mspPackage.PatchCode; - } - - if (!String.IsNullOrEmpty(key) && !this.DependencySymbolsByKey.ContainsKey(key)) - { - var dependency = this.Section.AddSymbol(new WixDependencyProviderSymbol(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.Id) - { - ParentRef = facade.PackageId, - ProviderKey = key, - Version = facade.PackageSymbol.Version, - DisplayName = facade.PackageSymbol.DisplayName - }); - - this.DependencySymbolsByKey.Add(dependency.ProviderKey, dependency); - } - } - } - - private Dictionary GetDependencySymbolsByKey(IEnumerable dependencySymbols) - { - var dependencySymbolsByKey = new Dictionary(); - - foreach (var dependency in dependencySymbols) - { - if (dependencySymbolsByKey.TryGetValue(dependency.ProviderKey, out var collision)) - { - // If not a perfect dependency collision, display an error. - if (dependency.ProviderKey != collision.ProviderKey || - dependency.Version != collision.Version || - dependency.DisplayName != collision.DisplayName) - { - this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.ProviderKey, dependency.ParentRef)); - } - } - else - { - dependencySymbolsByKey.Add(dependency.ProviderKey, dependency); - } - } - - return dependencySymbolsByKey; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs b/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs deleted file mode 100644 index c678b114..00000000 --- a/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bind -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ResolveDownloadUrlsCommand - { - public ResolveDownloadUrlsCommand(IMessaging messaging, IEnumerable backendExtensions, IEnumerable containers, Dictionary payloadsById) - { - this.Messaging = messaging; - this.BackendExtensions = backendExtensions; - this.Containers = containers; - this.PayloadsById = payloadsById; - } - - private IMessaging Messaging { get; } - - private IEnumerable BackendExtensions { get; } - - private IEnumerable Containers { get; } - - private Dictionary PayloadsById { get; } - - public void Execute() - { - this.ResolveContainerUrls(); - - this.ResolvePayloadUrls(); - } - - private void ResolveContainerUrls() - { - foreach (var container in this.Containers) - { - if (container.Type == ContainerType.Detached) - { - var resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - container.DownloadUrl = resolvedUrl; - } - } - else if (container.Type == ContainerType.Attached) - { - if (!String.IsNullOrEmpty(container.DownloadUrl)) - { - this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id)); - } - } - } - } - - private void ResolvePayloadUrls() - { - foreach (var payload in this.PayloadsById.Values) - { - if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName) - { - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id)); - } - } - else - { - var packageId = payload.ParentPackagePayloadRef; - var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl; - var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - payload.DownloadUrl = resolvedUrl; - } - } - } - } - - private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) - { - string resolvedUrl = null; - - foreach (var extension in this.BackendExtensions) - { - resolvedUrl = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - break; - } - } - - if (String.IsNullOrEmpty(resolvedUrl)) - { - // If a URL was not specified but there is a fallback URL that has a format specifier in it - // then use the fallback URL formatter for this URL. - if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl)) - { - var formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName); - if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase)) - { - url = fallbackUrl; - } - } - - if (!String.IsNullOrEmpty(url)) - { - var formattedUrl = String.Format(url, packageId, payloadId, fileName); - - if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var canonicalUri)) - { - resolvedUrl = canonicalUri.AbsoluteUri; - } - else - { - resolvedUrl = null; - } - } - } - - return resolvedUrl; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs deleted file mode 100644 index e88f26ef..00000000 --- a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System.Xml; - using WixToolset.Data.Symbols; - - internal class SetVariableSearchFacade : BaseSearchFacade - { - public SetVariableSearchFacade(WixSearchSymbol searchSymbol, WixSetVariableSymbol setVariableSymbol) - { - this.SearchSymbol = searchSymbol; - this.SetVariableSymbol = setVariableSymbol; - } - - private WixSetVariableSymbol SetVariableSymbol { get; } - - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("SetVariable"); - - base.WriteXml(writer); - - if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown) - { - writer.WriteAttributeString("Value", this.SetVariableSymbol.Value); - - switch (this.SetVariableSymbol.Type) - { - case WixBundleVariableType.Formatted: - writer.WriteAttributeString("Type", "formatted"); - break; - case WixBundleVariableType.Numeric: - writer.WriteAttributeString("Type", "numeric"); - break; - case WixBundleVariableType.String: - writer.WriteAttributeString("Type", "string"); - break; - case WixBundleVariableType.Version: - writer.WriteAttributeString("Type", "version"); - break; - } - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs deleted file mode 100644 index 60e9ea60..00000000 --- a/src/WixToolset.Core.Burn/BundleBackend.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System; - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Inscribe; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BundleBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - var command = new BindBundleCommand(context, backendExtensions); - command.Execute(); - - var result = context.ServiceProvider.GetService(); - result.FileTransfers = command.FileTransfers; - result.TrackedFiles = command.TrackedFiles; - result.Wixout = command.Wixout; - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - return result; - } - - public IDecompileResult Decompile(IDecompileContext context) - { - throw new NotImplementedException(); - } - - 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 Intermediate Unbind(IUnbindContext context) - { - var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); - var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); - var messaging = context.ServiceProvider.GetService(); - - using (var reader = BurnReader.Open(messaging, 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 deleted file mode 100644 index 75c60e56..00000000 --- a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class AutomaticallySlipstreamPatchesCommand - { - public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection packageFacades) - { - this.Section = section; - this.PackageFacades = packageFacades; - } - - private IntermediateSection Section { get; } - - private IEnumerable PackageFacades { get; } - - public void Execute() - { - var msiPackages = new List(); - var targetsProductCode = new Dictionary>(); - var targetsUpgradeCode = new Dictionary>(); - - foreach (var facade in this.PackageFacades) - { - // Keep track of all MSI packages. - if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - msiPackages.Add(msiPackage); - } - else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage && mspPackage.Slipstream) - { - var patchTargetCodeSymbols = this.Section.Symbols - .OfType() - .Where(r => r.PackageRef == facade.PackageId); - - // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. - foreach (var symbol in patchTargetCodeSymbols) - { - if (symbol.TargetsProductCode) - { - if (!targetsProductCode.TryGetValue(symbol.TargetCode, out var symbols)) - { - symbols = new List(); - targetsProductCode.Add(symbol.TargetCode, symbols); - } - - symbols.Add(symbol); - } - else if (symbol.TargetsUpgradeCode) - { - if (!targetsUpgradeCode.TryGetValue(symbol.TargetCode, out var symbols)) - { - symbols = new List(); - targetsUpgradeCode.Add(symbol.TargetCode, symbols); - } - } - } - } - } - - var slipstreamMspIds = new HashSet(); - - // Loop through the MSI and slipstream patches targeting it. - foreach (var msi in msiPackages) - { - if (targetsProductCode.TryGetValue(msi.ProductCode, out var symbols)) - { - foreach (var symbol in symbols) - { - Debug.Assert(symbol.TargetsProductCode); - Debug.Assert(!symbol.TargetsUpgradeCode); - - this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); - } - } - - if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out symbols)) - { - foreach (var symbol in symbols) - { - Debug.Assert(!symbol.TargetsProductCode); - Debug.Assert(symbol.TargetsUpgradeCode); - - this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); - } - - symbols = null; - } - } - } - - private bool TryAddSlipstreamSymbol(HashSet slipstreamMspIds, WixBundleMsiPackageSymbol msiPackage, WixBundlePatchTargetCodeSymbol patchTargetCode) - { - var id = new Identifier(AccessModifier.Section, msiPackage.Id.Id, patchTargetCode.PackageRef); - - if (slipstreamMspIds.Add(id.Id)) - { - this.Section.AddSymbol(new WixBundleSlipstreamMspSymbol(patchTargetCode.SourceLineNumbers) - { - TargetPackageRef = msiPackage.Id.Id, - MspPackageRef = patchTargetCode.PackageRef, - }); - - return true; - } - - return false; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs b/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs deleted file mode 100644 index 3b4a4156..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.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.Core.Burn.Bundles -{ - using System.IO; - using System.Security.Cryptography; - using System.Text; - - internal static class BundleHashAlgorithm - { - public static string Hash(FileInfo fileInfo) - { - byte[] hashBytes; - - using (var managed = new SHA512CryptoServiceProvider()) - using (var stream = fileInfo.OpenRead()) - { - hashBytes = managed.ComputeHash(stream); - } - - var sb = new StringBuilder(hashBytes.Length * 2); - for (var i = 0; i < hashBytes.Length; i++) - { - sb.AppendFormat("{0:X2}", hashBytes[i]); - } - - return sb.ToString(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs deleted file mode 100644 index 1eb3563a..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// 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 BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; - - public const string BADataFileName = "BootstrapperApplicationData.xml"; - public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; - - public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; - public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; - - // 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. - public BurnCommon(IMessaging messaging, string fileExe) - { - this.Messaging = messaging; - 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; } - - protected IMessaging Messaging { get; } - - public void Dispose() - { - this.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) - { - var bytes = new byte[4096]; - var total = 0; - do - { - var 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 (!this.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) - { - this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); - return false; - } - - this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); - if (BURN_SECTION_VERSION != this.Version) - { - this.Messaging.Write(ErrorMessages.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) - { - this.Messaging.Write(ErrorMessages.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 (!this.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) - { - this.Messaging.Write(ErrorMessages.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)) - { - this.Messaging.Write(ErrorMessages.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 (!this.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)) - { - this.Messaging.Write(ErrorMessages.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)) - { - this.Messaging.Write(ErrorMessages.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 - internal 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 - internal static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 4 <= bytes.Length); - return BurnCommon.ReadUInt16(bytes, offset) + ((UInt32)BurnCommon.ReadUInt16(bytes, offset + 2) << 16); - } - - /// - /// 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 - internal 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 deleted file mode 100644 index 5b06b31e..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BurnReader.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Xml; - using WixToolset.Core.Native; - using WixToolset.Extensibility.Services; - - /// - /// 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 readonly List attachedContainerPayloadNames; - - /// - /// Creates a BurnReader for reading a PE file. - /// - /// - /// File to read. - private BurnReader(IMessaging messaging, string fileExe) - : base(messaging, fileExe) - { - this.attachedContainerPayloadNames = new List(); - } - - /// - /// Gets the underlying stream. - /// - public Stream Stream => this.binaryReader?.BaseStream; - - internal static BurnReader Open(object inputFilePath) - { - throw new NotImplementedException(); - } - - /// - /// Opens a Burn reader. - /// - /// - /// Path to file. - /// Burn reader. - public static BurnReader Open(IMessaging messaging, string fileExe) - { - var reader = new BurnReader(messaging, 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. - /// Scratch directory. - /// 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); - } - - var cabinet = new Cabinet(tempCabPath); - cabinet.Extract(outputDirectory); - - Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); - FileSystem.MoveFile(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)); - FileSystem.MoveFile(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. - /// Scratch directory. - /// 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); - } - - var cabinet = new Cabinet(tempCabPath); - cabinet.Extract(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)); - FileSystem.MoveFile(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 deleted file mode 100644 index 2d16d11c..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// 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. - private BurnWriter(IMessaging messaging, string fileExe) - : base(messaging, fileExe) - { - } - - /// - /// Opens a Burn writer. - /// - /// - /// Path to file. - /// Burn writer. - public static BurnWriter Open(IMessaging messaging, string fileExe) - { - BurnWriter writer = new BurnWriter(messaging, 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, string bundleId) - { - if (this.invalidBundle) - { - return false; - } - - var bundleGuid = Guid.Parse(bundleId); - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); - - this.Messaging.Write(VerboseMessages.BundleGuid(bundleId)); - this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); - this.binaryWriter.Write(bundleGuid.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 this.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. - /// Size of the container. - /// Offset of size field for this container in ".wixburn" section data. - /// Number of Burn sections. - /// 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 deleted file mode 100644 index a0ee606d..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - - internal class CreateBootstrapperApplicationManifestCommand - { - public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadSymbols, Dictionary> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) - { - this.Section = section; - this.BundleSymbol = bundleSymbol; - this.ChainPackages = chainPackages; - this.LastUXPayloadIndex = lastUXPayloadIndex; - this.Payloads = payloadSymbols; - this.PackagesPayloads = packagesPayloads; - this.IntermediateFolder = intermediateFolder; - this.InternalBurnBackendHelper = internalBurnBackendHelper; - } - - private IntermediateSection Section { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private IEnumerable ChainPackages { get; } - - private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } - - private int LastUXPayloadIndex { get; } - - private Dictionary Payloads { get; } - - private Dictionary> PackagesPayloads { get; } - - private string IntermediateFolder { get; } - - public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; } - - public string OutputPath { get; private set; } - - public void Execute() - { - this.OutputPath = this.CreateBootstrapperApplicationManifest(); - - this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath); - } - - private string CreateBootstrapperApplicationManifest() - { - var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (var writer = new XmlTextWriter(path, Encoding.Unicode)) - { - writer.Formatting = Formatting.Indented; - writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); - - this.WriteBundleInfo(writer); - - this.WritePackageInfo(writer); - - this.WriteFeatureInfo(writer); - - this.WritePayloadInfo(writer); - - this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - - return path; - } - - private void WriteBundleInfo(XmlTextWriter writer) - { - writer.WriteStartElement("WixBundleProperties"); - - writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); - writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable); - writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no"); - writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant()); - writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode); - writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); - - writer.WriteEndElement(); - } - - private void WritePackageInfo(XmlTextWriter writer) - { - foreach (var package in this.ChainPackages) - { - if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) - { - continue; - } - - var packagePayload = payloads[package.PackageSymbol.PayloadRef]; - - var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); - - writer.WriteStartElement("WixPackageProperties"); - - writer.WriteAttributeString("Package", package.PackageId); - writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == true ? "yes" : "no"); - - if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName)) - { - writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.Description)) - { - writer.WriteAttributeString("Description", package.PackageSymbol.Description); - } - - writer.WriteAttributeString("DownloadSize", size); - writer.WriteAttributeString("PackageSize", size); - writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); - writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString()); - writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); - writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); - writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); - writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); - - if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - if (!String.IsNullOrEmpty(msiPackage.ProductCode)) - { - writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); - } - - if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); - } - } - else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) - { - if (!String.IsNullOrEmpty(mspPackage.PatchCode)) - { - writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); - } - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.Version)) - { - writer.WriteAttributeString("Version", package.PackageSymbol.Version); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) - { - writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); - } - - switch (package.PackageSymbol.Cache) - { - case YesNoAlwaysType.No: - writer.WriteAttributeString("Cache", "remove"); - break; - case YesNoAlwaysType.Yes: - writer.WriteAttributeString("Cache", "keep"); - break; - case YesNoAlwaysType.Always: - writer.WriteAttributeString("Cache", "force"); - break; - } - - writer.WriteEndElement(); - } - } - - private void WriteFeatureInfo(XmlTextWriter writer) - { - var featureSymbols = this.Section.Symbols.OfType(); - - foreach (var featureSymbol in featureSymbols) - { - writer.WriteStartElement("WixPackageFeatureInfo"); - - writer.WriteAttributeString("Package", featureSymbol.PackageRef); - writer.WriteAttributeString("Feature", featureSymbol.Name); - writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture)); - - if (!String.IsNullOrEmpty(featureSymbol.Parent)) - { - writer.WriteAttributeString("Parent", featureSymbol.Parent); - } - - if (!String.IsNullOrEmpty(featureSymbol.Title)) - { - writer.WriteAttributeString("Title", featureSymbol.Title); - } - - if (!String.IsNullOrEmpty(featureSymbol.Description)) - { - writer.WriteAttributeString("Description", featureSymbol.Description); - } - - writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Directory", featureSymbol.Directory); - writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture)); - - writer.WriteEndElement(); - } - } - - private void WritePayloadInfo(XmlTextWriter writer) - { - foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) - { - var packageId = kvp.Key; - var payloadsById = kvp.Value; - - foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) - { - this.WritePayloadInfo(writer, payloadSymbol, packageId); - } - } - - foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal)) - { - this.WritePayloadInfo(writer, payloadSymbol, null); - } - } - - private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId) - { - writer.WriteStartElement("WixPayloadProperties"); - - if (!String.IsNullOrEmpty(packageId)) - { - writer.WriteAttributeString("Package", packageId); - } - - writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); - - if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) - { - writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); - } - - writer.WriteAttributeString("Name", payloadSymbol.Name); - writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - - if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); - } - - writer.WriteEndElement(); - } - - private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) - { - var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName); - - var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = BurnCommon.BADataFileName, - SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, - Compressed = true, - UnresolvedSourceFile = baManifestPath, - ContainerRef = BurnConstants.BurnUXContainerName, - EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), - Packaging = PackagingType.Embedded, - }); - - var fileInfo = new FileInfo(baManifestPath); - - symbol.FileSize = (int)fileInfo.Length; - - symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); - - return symbol; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs deleted file mode 100644 index b802f556..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Reflection; - using System.Text; - using System.Xml; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Dtf.Resources; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CreateBundleExeCommand - { - public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.IntermediateFolder = intermediateFolder; - this.OutputPath = outputPath; - this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; - this.BundleSymbol = bundleSymbol; - this.UXContainer = uxContainer; - this.Containers = containers; - } - - public IFileTransfer Transfer { get; private set; } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private string IntermediateFolder { get; } - - private string OutputPath { get; } - - private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private WixBundleContainerSymbol UXContainer { get; } - - private IEnumerable Containers { get; } - - public void Execute() - { - var bundleFilename = Path.GetFileName(this.OutputPath); - - // Copy the burn.exe to a writable location then mark it to be moved to its final build location. - - var stubPlatform = this.BundleSymbol.Platform.ToString(); - var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); - - if (stubPlatform != "X86") - { - this.Messaging.Write(WarningMessages.ExperimentalBundlePlatform(stubPlatform)); - } - - var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename); - - this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile)); - - if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename)); - } - - this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers); - - FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); - File.SetAttributes(bundleTempPath, FileAttributes.Normal); - - var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol); - - var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion); - - UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData); - - // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers - // if they should be attached. - using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath)) - { - var burnStubFile = new FileInfo(bundleTempPath); - writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId); - - // Always attach the UX container first - writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX); - - // Now append all other attached containers - foreach (var container in this.Containers) - { - if (ContainerType.Attached == container.Type) - { - // The container was only created if it had payloads. - if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) - { - writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); - } - } - } - } - } - - private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) - { - const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1"; - const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3"; - const string compatv1Namespace = "urn:schemas-microsoft-com:compatibility.v1"; - const string ws2005Namespace = "http://schemas.microsoft.com/SMI/2005/WindowsSettings"; - const string ws2016Namespace = "http://schemas.microsoft.com/SMI/2016/WindowsSettings"; - const string ws2017Namespace = "http://schemas.microsoft.com/SMI/2017/WindowsSettings"; - - var bundleFileName = Path.GetFileName(outputPath); - var bundleAssemblyVersion = windowsAssemblyVersion.ToString(); - var bundlePlatform = bundleSymbol.Platform == Platform.X64 ? "amd64" : bundleSymbol.Platform.ToString().ToLower(); - var bundleDescription = bundleSymbol.Name; - - using (var memoryStream = new MemoryStream()) - using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8)) - { - writer.WriteStartDocument(); - - writer.WriteStartElement("assembly", asmv1Namespace); - writer.WriteAttributeString("manifestVersion", "1.0"); - - writer.WriteStartElement("assemblyIdentity"); - writer.WriteAttributeString("name", bundleFileName); - writer.WriteAttributeString("version", bundleAssemblyVersion); - writer.WriteAttributeString("processorArchitecture", bundlePlatform); - writer.WriteAttributeString("type", "win32"); - writer.WriteEndElement(); // - - if (!String.IsNullOrEmpty(bundleDescription)) - { - writer.WriteStartElement("description"); - writer.WriteString(bundleDescription); - writer.WriteEndElement(); - } - - writer.WriteStartElement("dependency"); - writer.WriteStartElement("dependentAssembly"); - writer.WriteStartElement("assemblyIdentity"); - writer.WriteAttributeString("name", "Microsoft.Windows.Common-Controls"); - writer.WriteAttributeString("version", "6.0.0.0"); - writer.WriteAttributeString("processorArchitecture", bundlePlatform); - writer.WriteAttributeString("publicKeyToken", "6595b64144ccf1df"); - writer.WriteAttributeString("language", "*"); - writer.WriteAttributeString("type", "win32"); - writer.WriteEndElement(); // - writer.WriteEndElement(); // - writer.WriteEndElement(); // - - writer.WriteStartElement("compatibility", compatv1Namespace); - writer.WriteStartElement("application"); - - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{e2011457-1546-43c5-a5fe-008deee3d3f0}"); // Windows Vista - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"); // Windows 7 - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"); // Windows 8 - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"); // Windows 8.1 - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"); // Windows 10 - writer.WriteEndElement(); - - writer.WriteEndElement(); // - writer.WriteEndElement(); // - - writer.WriteStartElement("trustInfo", asmv3Namespace); - writer.WriteStartElement("security"); - writer.WriteStartElement("requestedPrivileges"); - writer.WriteStartElement("requestedExecutionLevel"); - writer.WriteAttributeString("level", "asInvoker"); - writer.WriteAttributeString("uiAccess", "false"); - writer.WriteEndElement(); // - writer.WriteEndElement(); // - writer.WriteEndElement(); // - writer.WriteEndElement(); // - - if (bootstrapperApplicationSymbol.DpiAwareness != WixBootstrapperApplicationDpiAwarenessType.Unaware) - { - string dpiAwareValue = null; - string dpiAwarenessValue = null; - string gdiScalingValue = null; - - switch(bootstrapperApplicationSymbol.DpiAwareness) - { - case WixBootstrapperApplicationDpiAwarenessType.GdiScaled: - gdiScalingValue = "true"; - break; - case WixBootstrapperApplicationDpiAwarenessType.PerMonitor: - dpiAwareValue = "true/pm"; - break; - case WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2: - dpiAwareValue = "true/pm"; - dpiAwarenessValue = "PerMonitorV2, PerMonitor"; - break; - case WixBootstrapperApplicationDpiAwarenessType.System: - dpiAwareValue = "true"; - break; - } - - writer.WriteStartElement("application", asmv3Namespace); - writer.WriteStartElement("windowsSettings"); - - if (dpiAwareValue != null) - { - writer.WriteStartElement("dpiAware", ws2005Namespace); - writer.WriteString(dpiAwareValue); - writer.WriteEndElement(); - } - - if (dpiAwarenessValue != null) - { - writer.WriteStartElement("dpiAwareness", ws2016Namespace); - writer.WriteString(dpiAwarenessValue); - writer.WriteEndElement(); - } - - if (gdiScalingValue != null) - { - writer.WriteStartElement("gdiScaling", ws2017Namespace); - writer.WriteString(gdiScalingValue); - writer.WriteEndElement(); - } - - writer.WriteEndElement(); // - writer.WriteEndElement(); // - } - - writer.WriteEndDocument(); // - writer.Close(); - - return memoryStream.ToArray(); - } - } - - private static Version GetWindowsAssemblyVersion(WixBundleSymbol bundleSymbol) - { - // Ensure the bundle info provides a full four part version. - var fourPartVersion = new Version(bundleSymbol.Version); - var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; - var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; - var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; - var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; - - if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) - { - throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleSymbol.SourceLineNumbers, "Bundle", bundleSymbol.Version)); - } - - return new Version(major, minor, build, revision); - } - - private static void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version windowsAssemblyVersion, byte[] applicationManifestData) - { - const int burnLocale = 1033; - var resources = new Dtf.Resources.ResourceCollection(); - var version = new Dtf.Resources.VersionResource("#1", burnLocale); - - version.Load(bundleTempPath); - resources.Add(version); - - version.FileVersion = windowsAssemblyVersion; - version.ProductVersion = windowsAssemblyVersion; - - var strings = version[burnLocale] ?? version.Add(burnLocale); - 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.Manufacturer)) - { - strings["CompanyName"] = bundleInfo.Manufacturer; - } - else - { - strings["CompanyName"] = String.Empty; - } - - if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile)) - { - var iconGroup = new Dtf.Resources.GroupIconResource("#1", burnLocale); - iconGroup.ReadFromFile(bundleInfo.IconSourceFile); - resources.Add(iconGroup); - - foreach (var icon in iconGroup.Icons) - { - resources.Add(icon); - } - } - - if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile)) - { - var bitmap = new Dtf.Resources.BitmapResource("#1", burnLocale); - bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile); - resources.Add(bitmap); - } - - var manifestResource = new Resource(ResourceType.Manifest, "#1", burnLocale, applicationManifestData); - resources.Add(manifestResource); - - resources.Save(bundleTempPath); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs deleted file mode 100644 index e587413e..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Globalization; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - - internal class CreateBundleExtensionManifestCommand - { - public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) - { - this.Section = section; - this.BundleSymbol = bundleSymbol; - this.LastUXPayloadIndex = lastUXPayloadIndex; - this.IntermediateFolder = intermediateFolder; - this.InternalBurnBackendHelper = internalBurnBackendHelper; - } - - private IntermediateSection Section { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } - - private int LastUXPayloadIndex { get; } - - private string IntermediateFolder { get; } - - public WixBundlePayloadSymbol BundleExtensionManifestPayloadRow { get; private set; } - - public string OutputPath { get; private set; } - - public void Execute() - { - this.OutputPath = this.CreateBundleExtensionManifest(); - - this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(this.OutputPath); - } - - private string CreateBundleExtensionManifest() - { - var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (var writer = new XmlTextWriter(path, Encoding.Unicode)) - { - writer.Formatting = Formatting.Indented; - writer.WriteStartDocument(); - writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); - - this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - - return path; - } - - private WixBundlePayloadSymbol CreateBundleExtensionManifestPayloadRow(string bextManifestPath) - { - var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); - - this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers) - { - ParentType = ComplexReferenceParentType.Container, - ParentId = BurnConstants.BurnUXContainerName, - ChildType = ComplexReferenceChildType.Payload, - ChildId = generatedId - }); - - var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = BurnCommon.BundleExtensionDataFileName, - SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, - Compressed = true, - UnresolvedSourceFile = bextManifestPath, - ContainerRef = BurnConstants.BurnUXContainerName, - EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), - Packaging = PackagingType.Embedded, - }); - - var fileInfo = new FileInfo(bextManifestPath); - - symbol.FileSize = (int)fileInfo.Length; - - symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); - - return symbol; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs deleted file mode 100644 index 5655d23d..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ /dev/null @@ -1,700 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class CreateBurnManifestCommand - { - public CreateBurnManifestCommand(string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, Dictionary> packagesPayloads, IEnumerable orderedSearches, string intermediateFolder) - { - this.ExecutableName = executableName; - this.Section = section; - this.BundleSymbol = bundleSymbol; - this.Chain = chainSymbol; - this.Containers = containers; - this.OrderedPackages = orderedPackages; - this.RollbackBoundaries = boundaries; - this.UXContainerPayloads = uxPayloads; - this.Payloads = allPayloadsById; - this.PackagesPayloads = packagesPayloads; - this.OrderedSearches = orderedSearches; - this.IntermediateFolder = intermediateFolder; - } - - public string OutputPath { get; private set; } - - private string ExecutableName { get; } - - private IntermediateSection Section { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private WixChainSymbol Chain { get; } - - private IEnumerable RollbackBoundaries { get; } - - private IEnumerable OrderedPackages { get; } - - private IEnumerable OrderedSearches { get; } - - private Dictionary Payloads { get; } - - private Dictionary> PackagesPayloads { get; } - - private IEnumerable Containers { get; } - - private IEnumerable UXContainerPayloads { get; } - - private string IntermediateFolder { get; } - - public void Execute() - { - this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); - - using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) - { - writer.WriteStartDocument(); - - writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); - - // Write the condition, if there is one - if (null != this.BundleSymbol.Condition) - { - writer.WriteElementString("Condition", this.BundleSymbol.Condition); - } - - // Write the log element if default logging wasn't disabled. - if (!String.IsNullOrEmpty(this.BundleSymbol.LogPrefix)) - { - writer.WriteStartElement("Log"); - if (!String.IsNullOrEmpty(this.BundleSymbol.LogPathVariable)) - { - writer.WriteAttributeString("PathVariable", this.BundleSymbol.LogPathVariable); - } - writer.WriteAttributeString("Prefix", this.BundleSymbol.LogPrefix); - writer.WriteAttributeString("Extension", this.BundleSymbol.LogExtension); - writer.WriteEndElement(); - } - - - // Get update if specified. - var updateSymbol = this.Section.Symbols.OfType().FirstOrDefault(); - - if (null != updateSymbol) - { - writer.WriteStartElement("Update"); - writer.WriteAttributeString("Location", updateSymbol.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). - var relatedBundles = this.Section.Symbols.OfType(); - var distinctRelatedBundles = new HashSet(); - - foreach (var relatedBundle in relatedBundles) - { - if (distinctRelatedBundles.Add(relatedBundle.BundleId)) - { - writer.WriteStartElement("RelatedBundle"); - writer.WriteAttributeString("Id", relatedBundle.BundleId); - writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); - writer.WriteEndElement(); - } - } - - // Write the variables - var variables = this.Section.Symbols.OfType(); - - foreach (var variable in variables) - { - writer.WriteStartElement("Variable"); - writer.WriteAttributeString("Id", variable.Id.Id); - if (variable.Type != WixBundleVariableType.Unknown) - { - writer.WriteAttributeString("Value", variable.Value); - - switch (variable.Type) - { - case WixBundleVariableType.Formatted: - writer.WriteAttributeString("Type", "formatted"); - break; - case WixBundleVariableType.Numeric: - writer.WriteAttributeString("Type", "numeric"); - break; - case WixBundleVariableType.String: - writer.WriteAttributeString("Type", "string"); - break; - case WixBundleVariableType.Version: - writer.WriteAttributeString("Type", "version"); - break; - } - } - writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); - writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); - writer.WriteEndElement(); - } - - // Write the searches - foreach (var searchinfo in this.OrderedSearches) - { - searchinfo.WriteXml(writer); - } - - // write the UX element - writer.WriteStartElement("UX"); - if (!String.IsNullOrEmpty(this.BundleSymbol.SplashScreenSourceFile)) - { - writer.WriteAttributeString("SplashScreen", "yes"); - } - - // write the UX allPayloads... - foreach (var payload in this.UXContainerPayloads) - { - this.WriteBurnManifestUXPayload(writer, payload); - } - - writer.WriteEndElement(); // - - foreach (var container in this.Containers) - { - if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) - { - writer.WriteStartElement("Container"); - this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); - writer.WriteEndElement(); - } - } - - foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName)) - { - this.WriteBurnManifestPayload(writer, payload); - } - - foreach (var rollbackBoundary in this.RollbackBoundaries) - { - writer.WriteStartElement("RollbackBoundary"); - writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); - writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes"); - writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no"); - writer.WriteEndElement(); - } - - // Write the registration information... - writer.WriteStartElement("Registration"); - - writer.WriteAttributeString("Id", this.BundleSymbol.BundleId); - writer.WriteAttributeString("ExecutableName", this.ExecutableName); - writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Tag", this.BundleSymbol.Tag); - writer.WriteAttributeString("Version", this.BundleSymbol.Version); - writer.WriteAttributeString("ProviderKey", this.BundleSymbol.ProviderKey); - - writer.WriteStartElement("Arp"); - writer.WriteAttributeString("Register", (this.BundleSymbol.DisableModify || this.BundleSymbol.SingleChangeUninstallButton) && this.BundleSymbol.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove. - writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); - writer.WriteAttributeString("DisplayVersion", this.BundleSymbol.Version); - - if (!String.IsNullOrEmpty(this.BundleSymbol.Manufacturer)) - { - writer.WriteAttributeString("Publisher", this.BundleSymbol.Manufacturer); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.HelpUrl)) - { - writer.WriteAttributeString("HelpLink", this.BundleSymbol.HelpUrl); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.HelpTelephone)) - { - writer.WriteAttributeString("HelpTelephone", this.BundleSymbol.HelpTelephone); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.AboutUrl)) - { - writer.WriteAttributeString("AboutUrl", this.BundleSymbol.AboutUrl); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.UpdateUrl)) - { - writer.WriteAttributeString("UpdateUrl", this.BundleSymbol.UpdateUrl); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.ParentName)) - { - writer.WriteAttributeString("ParentDisplayName", this.BundleSymbol.ParentName); - } - - if (this.BundleSymbol.DisableModify) - { - writer.WriteAttributeString("DisableModify", "yes"); - } - - if (this.BundleSymbol.DisableRemove) - { - writer.WriteAttributeString("DisableRemove", "yes"); - } - - if (this.BundleSymbol.SingleChangeUninstallButton) - { - writer.WriteAttributeString("DisableModify", "button"); - } - writer.WriteEndElement(); // - - // Get update registration if specified. - var updateRegistrationInfo = this.Section.Symbols.OfType().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(); // - } - - foreach (var bundleTagSymbol in this.Section.Symbols.OfType()) - { - writer.WriteStartElement("SoftwareTag"); - writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); - writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); - writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); - writer.WriteCData(bundleTagSymbol.Xml); - 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. - var targetCodesByPatch = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var msiFeaturesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); - var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); - var commandLinesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.WixBundlePackageRef); - - var dependenciesByPackage = this.Section.Symbols.OfType().ToLookup(p => p.ParentRef); - - - // Build up the list of target codes from all the MSPs in the chain. - var targetCodes = new List(); - - foreach (var package in this.OrderedPackages) - { - writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageSymbol.Type)); - - writer.WriteAttributeString("Id", package.PackageId); - - switch (package.PackageSymbol.Cache) - { - case YesNoAlwaysType.No: - writer.WriteAttributeString("Cache", "remove"); - break; - case YesNoAlwaysType.Yes: - writer.WriteAttributeString("Cache", "keep"); - break; - case YesNoAlwaysType.Always: - writer.WriteAttributeString("Cache", "force"); - break; - } - - writer.WriteAttributeString("CacheId", package.PackageSymbol.CacheId); - writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageSymbol.InstallSize)); - writer.WriteAttributeString("Size", Convert.ToString(package.PackageSymbol.Size)); - writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageSymbol.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); - writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == false ? "no" : "yes"); - - if (null != package.PackageSymbol.RollbackBoundaryRef) - { - writer.WriteAttributeString("RollbackBoundaryForward", package.PackageSymbol.RollbackBoundaryRef); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackBoundaryBackwardRef)) - { - writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageSymbol.RollbackBoundaryBackwardRef); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.LogPathVariable)) - { - writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackLogPathVariable)) - { - writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) - { - writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); - } - - if (package.SpecificPackageSymbol is WixBundleExePackageSymbol exePackage) // EXE - { - writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition); - writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand); - writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand); - writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand); - writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no"); - if (!String.IsNullOrEmpty(exePackage.ExeProtocol)) - { - writer.WriteAttributeString("Protocol", exePackage.ExeProtocol); - } - } - else if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) // MSI - { - writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); - writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Version", msiPackage.ProductVersion); - if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); - } - } - else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) // MSP - { - writer.WriteAttributeString("PatchCode", mspPackage.PatchCode); - writer.WriteAttributeString("PatchXml", mspPackage.PatchXml); - - // 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 (!mspPackage.TargetUnspecified) - { - var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id]; - - targetCodes.AddRange(patchTargetCodes); - } - else // we have a patch that targets the world, so throw the whole list away. - { - targetCodes = null; - } - } - } - else if (package.SpecificPackageSymbol is WixBundleMsuPackageSymbol msuPackage) // MSU - { - writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition); - writer.WriteAttributeString("KB", msuPackage.MsuKB); - } - - var packageMsiFeatures = msiFeaturesByPackage[package.PackageId]; - - foreach (var feature in packageMsiFeatures) - { - writer.WriteStartElement("MsiFeature"); - writer.WriteAttributeString("Id", feature.Name); - writer.WriteEndElement(); - } - - var packageMsiProperties = msiPropertiesByPackage[package.PackageId]; - - foreach (var 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(); - } - - var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId]; - - foreach (var slipstreamMsp in packageSlipstreamMsps) - { - writer.WriteStartElement("SlipstreamMsp"); - writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef); - writer.WriteEndElement(); - } - - var packageExitCodes = exitCodesByPackage[package.PackageId]; - - foreach (var 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(); - } - - var packageCommandLines = commandLinesByPackage[package.PackageId]; - - foreach (var 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. - var dependencies = dependenciesByPackage[package.PackageId]; - - foreach (var dependency in dependencies) - { - writer.WriteStartElement("Provides"); - writer.WriteAttributeString("Key", dependency.ProviderKey); - - if (!String.IsNullOrEmpty(dependency.Version)) - { - writer.WriteAttributeString("Version", dependency.Version); - } - - if (!String.IsNullOrEmpty(dependency.DisplayName)) - { - writer.WriteAttributeString("DisplayName", dependency.DisplayName); - } - - if (dependency.Imported) - { - // The package dependency was explicitly authored into the manifest. - writer.WriteAttributeString("Imported", "yes"); - } - - writer.WriteEndElement(); - } - - var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; - - foreach (var related in packageRelatedPackages) - { - writer.WriteStartElement("RelatedPackage"); - writer.WriteAttributeString("Id", related.RelatedId); - 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"); - - var 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 - var packagePayloadId = package.PackageSymbol.PayloadRef; - writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", packagePayloadId); - writer.WriteEndElement(); - - var packagePayloads = this.PackagesPayloads[package.PackageId]; - - foreach (var payload in packagePayloads.Values) - { - if (payload.Id.Id != packagePayloadId) - { - writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", payload.Id.Id); - writer.WriteEndElement(); - } - } - - writer.WriteEndElement(); // - } - writer.WriteEndElement(); // - - if (null != targetCodes) - { - foreach (var targetCode in targetCodes) - { - writer.WriteStartElement("PatchTargetCode"); - writer.WriteAttributeString("TargetCode", targetCode.TargetCode); - writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); - writer.WriteEndElement(); - } - } - - // Write the ApprovedExeForElevation elements. - var approvedExesForElevation = this.Section.Symbols.OfType(); - - foreach (var approvedExeForElevation in approvedExesForElevation) - { - writer.WriteStartElement("ApprovedExeForElevation"); - writer.WriteAttributeString("Id", approvedExeForElevation.Id.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(); - } - - // Write the BundleExtension elements. - var bundleExtensions = this.Section.Symbols.OfType(); - - foreach (var bundleExtension in bundleExtensions) - { - writer.WriteStartElement("BundleExtension"); - writer.WriteAttributeString("Id", bundleExtension.Id.Id); - writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); - - writer.WriteEndElement(); - } - - writer.WriteEndDocument(); // - } - } - - private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerSymbol container) - { - writer.WriteAttributeString("Id", container.Id.Id); - writer.WriteAttributeString("FileSize", container.Size.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", container.Hash); - - if (ContainerType.Detached == container.Type) - { - if (!String.IsNullOrEmpty(container.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); - } - - writer.WriteAttributeString("FilePath", container.Name); - } - else if (ContainerType.Attached == container.Type) - { - writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. - writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Attached", "yes"); - writer.WriteAttributeString("Primary", "yes"); - } - } - - private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) - { - writer.WriteStartElement("Payload"); - - writer.WriteAttributeString("Id", payload.Id.Id); - writer.WriteAttributeString("FilePath", payload.Name); - writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", payload.Hash); - - if (payload.LayoutOnly) - { - writer.WriteAttributeString("LayoutOnly", "yes"); - } - - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); - } - - switch (payload.Packaging) - { - case PackagingType.Embedded: // this means it's in a container. - Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef); - - writer.WriteAttributeString("Packaging", "embedded"); - writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - writer.WriteAttributeString("Container", payload.ContainerRef); - break; - - case PackagingType.External: - writer.WriteAttributeString("Packaging", "external"); - writer.WriteAttributeString("SourcePath", payload.Name); - break; - } - - writer.WriteEndElement(); - } - - private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) - { - Debug.Assert(PackagingType.Embedded == payload.Packaging); - Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef); - - writer.WriteStartElement("Payload"); - - // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used. - writer.WriteAttributeString("Id", payload.Id.Id); - writer.WriteAttributeString("FilePath", payload.Name); - writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", payload.Hash); - writer.WriteAttributeString("Packaging", "embedded"); - writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs deleted file mode 100644 index 87a63cc3..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Creates cabinet files. - /// - internal class CreateContainerCommand - { - public CreateContainerCommand(IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) - { - this.Payloads = payloads; - this.OutputPath = outputPath; - this.CompressionLevel = compressionLevel; - } - - public CreateContainerCommand(string manifestPath, IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) - { - this.ManifestFile = manifestPath; - this.Payloads = payloads; - this.OutputPath = outputPath; - this.CompressionLevel = compressionLevel; - } - - private CompressionLevel? CompressionLevel { get; } - - private string ManifestFile { get; } - - private string OutputPath { get; } - - private IEnumerable Payloads { get; } - - public string Hash { get; private set; } - - public long Size { get; private set; } - - public void Execute() - { - var cabinetPath = Path.GetFullPath(this.OutputPath); - - var files = new List(); - - // If a manifest was provided always add it as "payload 0" to the container. - if (!String.IsNullOrEmpty(this.ManifestFile)) - { - files.Add(new CabinetCompressFile(this.ManifestFile, "0")); - } - - files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId))); - - var cab = new Cabinet(cabinetPath); - cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Medium); - - // Now that the container is created, set the outputs of the command. - var fileInfo = new FileInfo(cabinetPath); - - this.Hash = BundleHashAlgorithm.Hash(fileInfo); - - this.Size = fileInfo.Length; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs deleted file mode 100644 index f020ed84..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CreateNonUXContainers - { - public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) - { - this.BackendHelper = backendHelper; - this.Messaging = messaging; - this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; - this.Containers = containerSymbols; - this.PayloadSymbols = payloadSymbols; - this.IntermediateFolder = intermediateFolder; - this.LayoutFolder = layoutFolder; - this.DefaultCompressionLevel = defaultCompressionLevel; - } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable TrackedFiles { get; private set; } - - public WixBundleContainerSymbol UXContainer { get; set; } - - public IEnumerable UXContainerPayloads { get; private set; } - - private IEnumerable Containers { get; } - - private IBackendHelper BackendHelper { get; } - - private IMessaging Messaging { get; } - - private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } - - private Dictionary PayloadSymbols { get; } - - private string IntermediateFolder { get; } - - private string LayoutFolder { get; } - - private CompressionLevel? DefaultCompressionLevel { get; } - - public void Execute() - { - var fileTransfers = new List(); - var trackedFiles = new List(); - var uxPayloadSymbols = new List(); - - var attachedContainerIndex = 1; // count starts at one because UX container is "0". - - var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef); - - foreach (var container in this.Containers) - { - var containerId = container.Id.Id; - - var containerPayloads = payloadsByContainer[containerId]; - - if (!containerPayloads.Any()) - { - if (containerId != BurnConstants.BurnDefaultAttachedContainerName) - { - this.Messaging.Write(BurnBackendWarnings.EmptyContainer(container.SourceLineNumbers, containerId)); - } - } - else if (BurnConstants.BurnUXContainerName == containerId) - { - this.UXContainer = container; - - container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); - container.AttachedContainerIndex = 0; - - // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first - // in the list since that is the Payload that Burn attempts to load. - var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id; - - foreach (var uxPayload in containerPayloads) - { - if (uxPayload.Id.Id == baPayloadId) - { - uxPayloadSymbols.Insert(0, uxPayload); - } - else - { - uxPayloadSymbols.Add(uxPayload); - } - } - } - else - { - container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); - - // Add detached containers to the list of file transfers. - if (ContainerType.Detached == container.Type) - { - var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers); - fileTransfers.Add(transfer); - } - else // update the attached container index. - { - Debug.Assert(ContainerType.Attached == container.Type); - - container.AttachedContainerIndex = attachedContainerIndex; - ++attachedContainerIndex; - } - } - } - - foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) - { - if (container.Type == ContainerType.Attached && attachedContainerIndex > 2 && container.Id.Id != BurnConstants.BurnDefaultAttachedContainerName) - { - this.Messaging.Write(BurnBackendErrors.MultipleAttachedContainersUnsupported(container.SourceLineNumbers, container.Id.Id)); - } - } - - if (!this.Messaging.EncounteredError) - { - foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) - { - this.CreateContainer(container, payloadsByContainer[container.Id.Id]); - trackedFiles.Add(this.BackendHelper.TrackFile(container.WorkingPath, TrackedFileType.Temporary, container.SourceLineNumbers)); - } - } - - this.UXContainerPayloads = uxPayloadSymbols; - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - } - - private void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads) - { - var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); - command.Execute(); - - container.Hash = command.Hash; - container.Size = command.Size; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs b/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs deleted file mode 100644 index bfb6b918..00000000 --- a/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class DetectPayloadCollisionsCommand - { - public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary containerSymbols, IEnumerable packages, Dictionary payloadSymbols, Dictionary> packagePayloads) - { - this.Messaging = messaging; - this.Containers = containerSymbols; - this.Packages = packages; - this.PayloadSymbols = payloadSymbols; - this.PackagePayloads = packagePayloads; - } - - private IMessaging Messaging { get; } - - private Dictionary Containers { get; } - - private IEnumerable Packages { get; } - - private Dictionary PayloadSymbols { get; } - - private Dictionary> PackagePayloads { get; } - - public void Execute() - { - this.DetectAttachedContainerCollisions(); - this.DetectExternalCollisions(); - this.DetectPackageCacheCollisions(); - } - - public void DetectAttachedContainerCollisions() - { - var attachedContainerPayloadsByNameByContainer = new Dictionary>(); - - foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded)) - { - var containerId = payload.ContainerRef; - var container = this.Containers[containerId]; - if (container.Type == ContainerType.Attached) - { - if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName)) - { - attachedContainerPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName); - } - - if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) - { - attachedContainerPayloadsByName.Add(payload.Name, payload); - } - else - { - if (containerId == BurnConstants.BurnUXContainerName) - { - this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); - this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); - } - else - { - this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); - this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); - } - } - } - } - } - - public void DetectExternalCollisions() - { - var externalPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External)) - { - if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol)) - { - externalPayloadsByName.Add(payload.Name, payload); - } - else - { - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name)); - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); - } - } - - foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached)) - { - if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol)) - { - externalPayloadsByName.Add(container.Name, container); - } - else - { - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name)); - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); - } - } - } - - public void DetectPackageCacheCollisions() - { - var packageCachePayloadsByNameByCacheId = new Dictionary>(); - - foreach (var packageFacade in this.Packages) - { - var packagePayloads = this.PackagePayloads[packageFacade.PackageId]; - if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName)) - { - packageCachePayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName); - } - - foreach (var payload in packagePayloads.Values) - { - if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) - { - packageCachePayloadsByName.Add(payload.Name, payload); - } - else - { - this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId)); - this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers)); - } - } - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs deleted file mode 100644 index b8b256fd..00000000 --- a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class GetPackageFacadesCommand - { - public GetPackageFacadesCommand(IMessaging messaging, IEnumerable chainPackageSymbols, IntermediateSection section) - { - this.Messaging = messaging; - this.ChainPackageSymbols = chainPackageSymbols; - this.Section = section; - } - - private IEnumerable ChainPackageSymbols { get; } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public IDictionary PackageFacades { get; private set; } - - public void Execute() - { - var wixGroupPackagesGroupedById = this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId); - var exePackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msiPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var mspPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msuPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var exePackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msiPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var mspPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msuPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - var facades = new Dictionary(); - - foreach (var package in this.ChainPackageSymbols) - { - var id = package.Id.Id; - - IntermediateSymbol packagePayload = null; - foreach (var wixGroup in wixGroupPackagesGroupedById[id]) - { - if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) - { - IntermediateSymbol tempPackagePayload = null; - if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload)) - { - if (package.Type == WixBundlePackageType.Exe) - { - tempPackagePayload = exePackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload)) - { - if (package.Type == WixBundlePackageType.Msi) - { - tempPackagePayload = msiPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload)) - { - if (package.Type == WixBundlePackageType.Msp) - { - tempPackagePayload = mspPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload)) - { - if (package.Type == WixBundlePackageType.Msu) - { - tempPackagePayload = msuPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId)); - } - - if (tempPackagePayload != null) - { - if (packagePayload == null) - { - packagePayload = tempPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id)); - this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers)); - this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers)); - } - } - } - } - - if (packagePayload == null) - { - this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString())); - } - else - { - package.PayloadRef = packagePayload.Id.Id; - } - - switch (package.Type) - { - case WixBundlePackageType.Exe: - if (exePackages.TryGetValue(id, out var exePackage)) - { - facades.Add(id, new PackageFacade(package, exePackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id)); - } - break; - - case WixBundlePackageType.Msi: - if (msiPackages.TryGetValue(id, out var msiPackage)) - { - facades.Add(id, new PackageFacade(package, msiPackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id)); - } - break; - - case WixBundlePackageType.Msp: - if (mspPackages.TryGetValue(id, out var mspPackage)) - { - facades.Add(id, new PackageFacade(package, mspPackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id)); - } - break; - - case WixBundlePackageType.Msu: - if (msuPackages.TryGetValue(id, out var msuPackage)) - { - facades.Add(id, new PackageFacade(package, msuPackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id)); - } - break; - } - } - - this.PackageFacades = facades; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs deleted file mode 100644 index ccf6b1c2..00000000 --- a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class OrderPackagesAndRollbackBoundariesCommand - { - public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, IDictionary packageFacades) - { - this.Messaging = messaging; - this.Section = section; - this.PackageFacades = packageFacades; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IDictionary PackageFacades { get; } - - public IEnumerable OrderedPackageFacades { get; private set; } - - public IEnumerable UsedRollbackBoundaries { get; private set; } - - public void Execute() - { - var groupSymbols = this.Section.Symbols.OfType().ToList(); - var boundariesById = this.Section.Symbols.OfType().ToDictionary(b => b.Id.Id); - - var orderedFacades = new List(); - var 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. - var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true }; - var lastRollbackBoundary = pendingRollbackBoundary; - var boundaryHadX86Package = false; - var warnedMsiTransaction = false; - - foreach (var groupSymbol in groupSymbols) - { - if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId) - { - if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade)) - { - var insideMsiTransaction = lastRollbackBoundary?.Transaction ?? false; - - if (null != pendingRollbackBoundary) - { - // If we used the default boundary, ensure the symbol is added to the section. - if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId) - { - this.Section.AddSymbol(pendingRollbackBoundary); - } - - if (insideMsiTransaction && !warnedMsiTransaction) - { - warnedMsiTransaction = true; - this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers)); - } - - usedBoundaries.Add(pendingRollbackBoundary); - facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id; - pendingRollbackBoundary = null; - - boundaryHadX86Package = !facade.PackageSymbol.Win64; - } - - // Error if MSI transaction has x86 package preceding x64 packages - if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64) - { - this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers)); - } - - boundaryHadX86Package |= !facade.PackageSymbol.Win64; - - orderedFacades.Add(facade); - } - else // must be a rollback boundary. - { - // Discard the next rollback boundary if we have a previously defined boundary. - var nextRollbackBoundary = boundariesById[groupSymbol.ChildId]; - if (null != pendingRollbackBoundary) - { - if (pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId) - { - this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); - } - } - - lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary; - } - } - } - - if (null != pendingRollbackBoundary) - { - this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id)); - } - - // 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 (var package in orderedFacades) - { - if (null != package.PackageSymbol.RollbackBoundaryRef) - { - if (null != previousFacade) - { - previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; - } - - previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef; - } - - previousFacade = package; - } - - if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) - { - previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; - } - - this.OrderedPackageFacades = orderedFacades; - this.UsedRollbackBoundaries = usedBoundaries; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs deleted file mode 100644 index f3afd64e..00000000 --- a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class OrderSearchesCommand - { - public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public IDictionary> ExtensionSearchSymbolsByExtensionId { get; private set; } - - public IEnumerable OrderedSearchFacades { get; private set; } - - public void Execute() - { - this.ExtensionSearchSymbolsByExtensionId = new Dictionary>(); - this.OrderedSearchFacades = Array.Empty(); - - var searchSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - if (searchSymbols.Count == 0) - { - // Nothing to do! - return; - } - - var constraints = new Constraints(); - - // Add relational info to our data... - foreach (var searchRelationSymbol in this.Section.Symbols.OfType()) - { - constraints.AddConstraint(searchRelationSymbol.Id.Id, searchRelationSymbol.ParentSearchRef); - } - - this.FindCircularReference(constraints); - - if (this.Messaging.EncounteredError) - { - return; - } - - this.FlattenDependentReferences(constraints); - - // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting) - // We use a variation of Kahn (1962) algorithm as described in - // Wikipedia, with the additional criteria that start nodes are sorted - // lexicographically at each step to ensure a deterministic ordering - // based on 'after' dependencies and ID. - var sorter = new TopologicalSort(); - var sortedIds = sorter.Sort(searchSymbols.Keys, constraints); - - // Now, create the search facades with the searches in order... - (var orderedSearchFacades, var extensionSearchSymbolsByExtensionId) = this.OrderSearches(sortedIds, searchSymbols); - - this.OrderedSearchFacades = orderedSearchFacades; - this.ExtensionSearchSymbolsByExtensionId = extensionSearchSymbolsByExtensionId; - } - - /// - /// A dictionary of constraints, mapping an id to a list of ids. - /// - private class Constraints : Dictionary> - { - public void AddConstraint(string id, string afterId) - { - if (!this.ContainsKey(id)) - { - this.Add(id, new List()); - } - - // TODO: Show warning if a constraint is seen twice? - if (!this[id].Contains(afterId)) - { - this[id].Add(afterId); - } - } - - // TODO: Hide other Add methods? - } - - /// - /// Finds circular references in the constraints. - /// - /// Constraints to check. - /// This is not particularly performant, but it works. - private void FindCircularReference(Constraints constraints) - { - foreach (var id in constraints.Keys) - { - var seenIds = new List(); - - if (this.FindCircularReference(constraints, id, id, seenIds, out var chain)) - { - // We will show a separate message for every ID that's in - // the loop. We could bail after the first one, but then - // we wouldn't catch disjoint loops in a single run. - this.Messaging.Write(ErrorMessages.CircularSearchReference(chain)); - } - } - } - - /// - /// Recursive function that finds circular references in the constraints. - /// - /// Constraints to check. - /// The identifier currently being looking for. (Fixed across a given run.) - /// The idenifier curently being tested. - /// A list of identifiers seen, to ensure each identifier is only expanded once. - /// If a circular reference is found, will contain the chain of references. - /// True if a circular reference is found, false otherwise. - private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List seenIds, out string chain) - { - chain = null; - if (constraints.TryGetValue(currentId, out var afterList)) - { - foreach (string afterId in afterList) - { - if (afterId == checkId) - { - chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId); - return true; - } - - if (!seenIds.Contains(afterId)) - { - seenIds.Add(afterId); - if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain)) - { - chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain); - return true; - } - } - } - } - - return false; - } - - /// - /// Flattens any dependency chains to simplify reordering. - /// - /// - private void FlattenDependentReferences(Constraints constraints) - { - foreach (string id in constraints.Keys) - { - var flattenedIds = new List(); - this.AddDependentReferences(constraints, id, flattenedIds); - var constraintList = constraints[id]; - foreach (var flattenedId in flattenedIds) - { - if (!constraintList.Contains(flattenedId)) - { - constraintList.Add(flattenedId); - } - } - } - } - - /// - /// Adds dependent references to a list. - /// - /// - /// - /// - private void AddDependentReferences(Constraints constraints, string currentId, List seenIds) - { - if (constraints.TryGetValue(currentId, out var afterList)) - { - foreach (var afterId in afterList) - { - if (!seenIds.Contains(afterId)) - { - seenIds.Add(afterId); - this.AddDependentReferences(constraints, afterId, seenIds); - } - } - } - } - - /// - /// Reorder by topological sort - /// - /// - /// We use a variation of Kahn (1962) algorithm as described in - /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with - /// the additional criteria that start nodes are sorted lexicographically - /// at each step to ensure a deterministic ordering based on 'after' - /// dependencies and ID. - /// - private class TopologicalSort - { - private readonly List startIds = new List(); - private Constraints constraints; - - /// - /// Reorder by topological sort - /// - /// The complete list of IDs. - /// Constraints to use. - /// The topologically sorted list of IDs. - internal List Sort(IEnumerable allIds, Constraints constraints) - { - this.startIds.Clear(); - this.CopyConstraints(constraints); - - this.FindInitialStartIds(allIds); - - // We always create a new sortedId list, because we return it - // to the caller and don't know what its lifetime may be. - var sortedIds = new List(); - - while (this.startIds.Count > 0) - { - this.SortStartIds(); - - var currentId = this.startIds[0]; - sortedIds.Add(currentId); - this.startIds.RemoveAt(0); - - this.ResolveConstraint(currentId); - } - - return sortedIds; - } - - /// - /// Copies a Constraints set (to prevent modifying the incoming data). - /// - /// Constraints to copy. - private void CopyConstraints(Constraints constraints) - { - this.constraints = new Constraints(); - foreach (var id in constraints.Keys) - { - foreach (var afterId in constraints[id]) - { - this.constraints.AddConstraint(id, afterId); - } - } - } - - /// - /// Finds initial start IDs. (Those with no constraints.) - /// - /// The complete list of IDs. - private void FindInitialStartIds(IEnumerable allIds) - { - foreach (var id in allIds) - { - if (!this.constraints.ContainsKey(id)) - { - this.startIds.Add(id); - } - } - } - - /// - /// Sorts start IDs. - /// - private void SortStartIds() - { - this.startIds.Sort(); - } - - /// - /// Removes the resolved constraint and updates the list of startIds - /// with any now-valid (all constraints resolved) IDs. - /// - /// The ID to resolve from the set of constraints. - private void ResolveConstraint(string resolvedId) - { - var newStartIds = new List(); - - foreach (var id in this.constraints.Keys) - { - if (this.constraints[id].Contains(resolvedId)) - { - this.constraints[id].Remove(resolvedId); - - // If we just removed the last constraint for this - // ID, it is now a valid start ID. - if (this.constraints[id].Count == 0) - { - newStartIds.Add(id); - } - } - } - - foreach (var id in newStartIds) - { - this.constraints.Remove(id); - } - - this.startIds.AddRange(newStartIds); - } - } - - private (IEnumerable, Dictionary>) OrderSearches(IEnumerable sortedIds, Dictionary searchSymbolDictionary) - { - var orderedSearchFacades = new List(); - var extensionSearchSymbolsByExtensionId = new Dictionary>(); - - // 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. - var legacySearchesById = this.Section.Symbols - .Where(t => t.Definition.Type == SymbolDefinitionType.WixComponentSearch || - t.Definition.Type == SymbolDefinitionType.WixFileSearch || - t.Definition.Type == SymbolDefinitionType.WixProductSearch || - t.Definition.Type == SymbolDefinitionType.WixRegistrySearch) - .ToDictionary(t => t.Id.Id); - var setVariablesById = this.Section.Symbols - .OfType() - .ToDictionary(t => t.Id.Id); - var extensionSearchesById = this.Section.Symbols - .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag)) - .ToDictionary(t => t.Id.Id); - - foreach (var searchId in sortedIds) - { - var searchSymbol = searchSymbolDictionary[searchId]; - - if (legacySearchesById.TryGetValue(searchId, out var specificSearchSymbol)) - { - orderedSearchFacades.Add(new LegacySearchFacade(searchSymbol, specificSearchSymbol)); - } - else if (setVariablesById.TryGetValue(searchId, out var setVariableSymbol)) - { - orderedSearchFacades.Add(new SetVariableSearchFacade(searchSymbol, setVariableSymbol)); - } - else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchSymbol)) - { - orderedSearchFacades.Add(new ExtensionSearchFacade(searchSymbol)); - - if (!extensionSearchSymbolsByExtensionId.TryGetValue(searchSymbol.BundleExtensionRef, out var extensionSearchSymbols)) - { - extensionSearchSymbols = new List(); - extensionSearchSymbolsByExtensionId[searchSymbol.BundleExtensionRef] = extensionSearchSymbols; - } - extensionSearchSymbols.Add(extensionSearchSymbol); - } - else - { - this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchSymbol.SourceLineNumbers, searchId)); - } - } - - return (orderedSearchFacades, extensionSearchSymbolsByExtensionId.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value)); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs deleted file mode 100644 index 471262de..00000000 --- a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System.Diagnostics; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class PackageFacade - { - public PackageFacade(WixBundlePackageSymbol packageSymbol, IntermediateSymbol specificPackageSymbol) - { - Debug.Assert(packageSymbol.Id.Id == specificPackageSymbol.Id.Id); - - this.PackageSymbol = packageSymbol; - this.SpecificPackageSymbol = specificPackageSymbol; - } - - public string PackageId => this.PackageSymbol.Id.Id; - - public WixBundlePackageSymbol PackageSymbol { get; } - - public IntermediateSymbol SpecificPackageSymbol { get; } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs deleted file mode 100644 index 8d8ea986..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.Symbols; - - /// - /// Initializes package state from the Exe contents. - /// - internal class ProcessExePackageCommand - { - public ProcessExePackageCommand(PackageFacade facade, Dictionary payloadSymbols) - { - this.AuthoredPayloads = payloadSymbols; - this.Facade = facade; - } - - public Dictionary AuthoredPayloads { get; } - - public PackageFacade Facade { get; } - - /// - /// Processes the Exe packages to add properties and payloads from the Exe packages. - /// - public void Execute() - { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = packagePayload.Hash; - } - - this.Facade.PackageSymbol.Version = packagePayload.Version; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs deleted file mode 100644 index 99e2eda5..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Core.Native.Msi; - - /// - /// Initializes package state from the MSI contents. - /// - internal class ProcessMsiPackageCommand - { - private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?"; - - public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary packagePayloads) - { - this.Messaging = serviceProvider.GetService(); - this.BackendHelper = serviceProvider.GetService(); - this.PathResolver = serviceProvider.GetService(); - - this.BackendExtensions = backendExtensions; - - this.PackagePayloads = packagePayloads; - this.Section = section; - this.Facade = facade; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IEnumerable BackendExtensions { get; } - - private Dictionary PackagePayloads { get; } - - private PackageFacade Facade { get; } - - private IntermediateSection Section { get; } - - /// - /// Processes the MSI packages to add properties and payloads from the MSI packages. - /// - public void Execute() - { - var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef]; - - var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol; - - var sourcePath = packagePayload.SourceFile.Path; - var longNamesInImage = false; - var compressed = false; - try - { - using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) - { - // Read data out of the msi database... - using (var sumInfo = new SummaryInformation(db)) - { - var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags); - var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages); - - // 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 == (fileAndElevateFlags & 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 == (fileAndElevateFlags & 2); - - // 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. - var perMachine = (0 == (fileAndElevateFlags & 8)); - var x64 = platformsAndLanguages.Contains("x64"); - - this.Facade.PackageSymbol.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; - this.Facade.PackageSymbol.Win64 = x64; - } - - string packageName = null; - string packageDescription = null; - string allusers = null; - string fastInstall = null; - string systemComponent = null; - - using (var view = db.OpenView(PropertySqlQuery)) - { - packageName = ProcessMsiPackageCommand.GetProperty(view, "ProductName"); - packageDescription = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS"); - allusers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS"); - fastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL"); - systemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT"); - - msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode"); - msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode"); - msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer"); - msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"), CultureInfo.InvariantCulture); - msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion"); - } - - if (!this.BackendHelper.IsValidFourPartVersion(msiPackage.ProductVersion)) - { - // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? - string version = null; - var versionParts = msiPackage.ProductVersion.Split('.'); - var count = versionParts.Length; - if (0 < count) - { - version = versionParts[0]; - for (var i = 1; i < 4 && i < count; ++i) - { - version = String.Concat(version, ".", versionParts[i]); - } - } - - if (!String.IsNullOrEmpty(version) && this.BackendHelper.IsValidFourPartVersion(version)) - { - this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version)); - msiPackage.ProductVersion = version; - } - else - { - this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath)); - } - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion); - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) - { - this.Facade.PackageSymbol.DisplayName = packageName; - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) - { - this.Facade.PackageSymbol.Description = packageDescription; - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Version)) - { - this.Facade.PackageSymbol.Version = msiPackage.ProductVersion; - } - - var payloadNames = this.GetPayloadTargetNames(); - - var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); - - this.SetPerMachineAppropriately(allusers, msiPackage, sourcePath); - - // Ensure the MSI package is appropriately marked visible or not. - this.SetPackageVisibility(systemComponent, msiPackage, msiPropertyNames); - - // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. - if (!String.IsNullOrEmpty(fastInstall)) - { - this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7"); - } - - this.CreateRelatedPackages(db); - - // If feature selection is enabled, represent the Feature table in the manifest. - if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.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.PackageSymbol.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); - - // Add all dependency providers from the MSI. - this.ImportDependencyProviders(db, msiPackage); - } - } - catch (MsiException e) - { - this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, e.Message)); - } - } - - private ISet GetPayloadTargetNames() - { - var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); - - return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); - } - - private ISet GetMsiPropertyNames(string packageId) - { - var properties = this.Section.Symbols.OfType() - .Where(p => p.PackageRef == packageId) - .Select(p => p.Name); - - return new HashSet(properties, StringComparer.Ordinal); - } - - private void SetPerMachineAppropriately(string allusers, WixBundleMsiPackageSymbol msiPackage, string sourcePath) - { - if (msiPackage.ForcePerMachine) - { - if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); - this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. - } - - // Force ALLUSERS=1 via the MSI command-line. - this.AddMsiProperty(msiPackage, "ALLUSERS", "1"); - } - else - { - if (String.IsNullOrEmpty(allusers)) - { - // Not forced per-machine and no ALLUSERS property, flip back to per-user. - if (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); - this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.No; - } - } - else if (allusers.Equals("1", StringComparison.Ordinal)) - { - if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); - } - } - else if (allusers.Equals("2", StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) ? "machine" : "user")); - } - else - { - this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, allusers)); - } - } - } - - private void SetPackageVisibility(string systemComponent, WixBundleMsiPackageSymbol msiPackage, ISet msiPropertyNames) - { - // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. - if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) - { - var alreadyVisible = String.IsNullOrEmpty(systemComponent); - var visible = (this.Facade.PackageSymbol.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible; - - // If not already set to the correct visibility. - if (alreadyVisible != visible) - { - this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); - } - } - } - - private void CreateRelatedPackages(Database db) - { - // Represent the Upgrade table as related packages. - if (db.TableExists("Upgrade")) - { - using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) - { - foreach (var record in view.Records) - { - var recordAttributes = record.GetInteger(5); - - var attributes = WixBundleRelatedPackageAttributes.None; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0; - - this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.Facade.PackageSymbol.SourceLineNumbers) - { - PackageRef = this.Facade.PackageId, - RelatedId = record.GetString(1), - MinVersion = record.GetString(2), - MaxVersion = record.GetString(3), - Languages = record.GetString(4), - Attributes = attributes, - }); - } - } - } - } - - private void CreateMsiFeatures(Database db) - { - if (db.TableExists("Feature")) - { - using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`")) - using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) - using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) - { - using (var featureRecord = new Record(1)) - using (var componentRecord = new Record(1)) - { - foreach (var allFeaturesResultRecord in allFeaturesView.Records) - { - var 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; - foreach (var componentResultRecord in featureView.Records) - { - var component = componentResultRecord.GetString(1); - componentRecord.SetString(1, component); - componentView.Execute(componentRecord); - - foreach (var fileResultRecord in componentView.Records) - { - var fileSize = fileResultRecord.GetString(1); - size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); - } - } - - this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, this.Facade.PackageId, featureName)) - { - PackageRef = this.Facade.PackageId, - Name = featureName, - Parent = allFeaturesResultRecord.GetString(2), - Title = allFeaturesResultRecord.GetString(3), - Description = allFeaturesResultRecord.GetString(4), - Display = allFeaturesResultRecord.GetInteger(5), - Level = allFeaturesResultRecord.GetInteger(6), - Directory = allFeaturesResultRecord.GetString(7), - Attributes = allFeaturesResultRecord.GetInteger(8), - Size = size - }); - } - } - } - } - } - - private void ImportExternalCabinetAsPayloads(Database db, WixBundlePayloadSymbol packagePayload, ISet payloadNames) - { - if (db.TableExists("Media")) - { - using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`")) - { - foreach (var cabinetRecord in view.Records) - { - var cabinet = cabinetRecord.GetString(1); - - 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... - var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); - - if (!payloadNames.Contains(cabinetName)) - { - var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); - var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers); - - this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) - { - ParentType = ComplexReferenceParentType.Package, - ParentId = this.Facade.PackageId, - ChildType = ComplexReferenceChildType.Payload, - ChildId = generatedId - }); - - this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = cabinetName, - SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, - Compressed = packagePayload.Compressed, - UnresolvedSourceFile = cabinetName, - ContainerRef = packagePayload.ContainerRef, - ContentFile = true, - Packaging = packagePayload.Packaging, - ParentPackagePayloadRef = packagePayload.Id.Id, - }); - } - } - } - } - } - } - - private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, WixBundlePayloadSymbol packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) - { - long size = 0; - - if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File")) - { - var directories = new Dictionary(); - - // Load up the directory hash table so we will be able to resolve source paths - // for files in the MSI database. - using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) - { - foreach (var record in view.Records) - { - var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage); - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); - - directories.Add(record.GetString(1), resolvedDirectory); - } - } - - // Resolve the source paths to external files and add each file size to the total - // install size of the package. - using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) - { - foreach (var record in view.Records) - { - // 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. - var compressionBit = record.GetInteger(4); - if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || - (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) - { - var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); - var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); - - if (!payloadNames.Contains(name)) - { - var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); - var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers); - - this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) - { - ParentType = ComplexReferenceParentType.Package, - ParentId = this.Facade.PackageId, - ChildType = ComplexReferenceChildType.Payload, - ChildId = generatedId - }); - - this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = name, - SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, - Compressed = packagePayload.Compressed, - UnresolvedSourceFile = name, - ContainerRef = packagePayload.ContainerRef, - ContentFile = true, - Packaging = packagePayload.Packaging, - ParentPackagePayloadRef = packagePayload.Id.Id, - }); - } - } - - size += record.GetInteger(5); - } - } - } - - return size; - } - - private void AddMsiProperty(WixBundleMsiPackageSymbol msiPackage, string name, string value) - { - this.Section.AddSymbol(new WixBundleMsiPropertySymbol(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Section, msiPackage.Id.Id, name)) - { - PackageRef = msiPackage.Id.Id, - Name = name, - Value = value, - }); - } - - private void ImportDependencyProviders(Database db, WixBundleMsiPackageSymbol msiPackage) - { - if (db.TableExists("WixDependencyProvider")) - { - using (var view = db.OpenExecuteView("SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`")) - { - foreach (var record in view.Records) - { - var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1))); - - // Import the provider key and attributes. - this.Section.AddSymbol(new WixDependencyProviderSymbol(msiPackage.SourceLineNumbers, id) - { - ParentRef = msiPackage.Id.Id, - ProviderKey = record.GetString(2), - Version = record.GetString(3) ?? msiPackage.ProductVersion, - DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName, - Attributes = WixDependencyProviderAttributes.ProvidesAttributesImported | (WixDependencyProviderAttributes)record.GetInteger(5), - }); - } - } - } - } - - private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers) - { - var checkedPaths = new List(); - - foreach (var extension in this.BackendExtensions) - { - var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); - - if (!File.Exists(resolvedPath)) - { - checkedPaths.Add(resolvedPath); - this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); - } - - return resolvedPath; - } - - private static string GetProperty(View view, string property) - { - using (var queryRecord = new Record(1)) - { - queryRecord[1] = property; - - view.Execute(queryRecord); - - using (var record = view.Fetch()) - { - return record?.GetString(1); - } - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs deleted file mode 100644 index 5f431b38..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Initializes package state from the Msp contents. - /// - internal class ProcessMspPackageCommand - { - private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?"; - private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings() - { - Encoding = new UTF8Encoding(false), - Indent = false, - NewLineChars = String.Empty, - NewLineHandling = NewLineHandling.Replace, - }; - - public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary payloadSymbols) - { - this.Messaging = messaging; - - this.AuthoredPayloads = payloadSymbols; - this.Section = section; - this.Facade = facade; - } - - public IMessaging Messaging { get; } - - public Dictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public IntermediateSection Section { get; } - - /// - /// Processes the Msp packages to add properties and payloads from the Msp packages. - /// - public void Execute() - { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; - - var mspPackage = (WixBundleMspPackageSymbol)this.Facade.SpecificPackageSymbol; - - var sourcePath = packagePayload.SourceFile.Path; - - try - { - using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - // Read data out of the msp database... - using (var sumInfo = new SummaryInformation(db)) - { - var patchCode = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode); - mspPackage.PatchCode = patchCode.Substring(0, 38); - } - - using (var view = db.OpenView(PatchMetadataQuery)) - { - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) - { - this.Facade.PackageSymbol.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName"); - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) - { - this.Facade.PackageSymbol.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description"); - } - - mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName"); - } - } - - this.ProcessPatchXml(packagePayload, mspPackage, sourcePath); - } - catch (MsiException e) - { - this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); - return; - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = mspPackage.PatchCode; - } - } - - private void ProcessPatchXml(WixBundlePayloadSymbol packagePayload, WixBundleMspPackageSymbol mspPackage, string sourcePath) - { - var uniqueTargetCodes = new HashSet(); - - var patchXml = Installer.ExtractPatchXml(sourcePath); - - var doc = new XmlDocument(); - doc.LoadXml(patchXml); - - var 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. - var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); - var 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 - { - mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; - } - } - - var targetCode = targetCodeElement.InnerText; - - if (uniqueTargetCodes.Add(targetCode)) - { - this.Section.AddSymbol(new WixBundlePatchTargetCodeSymbol(packagePayload.SourceLineNumbers) - { - PackageRef = packagePayload.Id.Id, - TargetCode = targetCode, - Attributes = attributes - }); - } - } - - // Suppress patch sequence data for improved performance. - var root = doc.DocumentElement; - foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) - { - root.RemoveChild(node); - } - - // Save the XML as compact as possible. - using (var writer = new StringWriter()) - { - using (var xmlWriter = XmlWriter.Create(writer, XmlSettings)) - { - doc.WriteTo(xmlWriter); - } - - mspPackage.PatchXml = writer.ToString(); - } - } - - private static string GetPatchMetadataProperty(View view, string property) - { - using (var queryRecord = new Record(1)) - { - queryRecord[1] = property; - - view.Execute(queryRecord); - - using (var record = view.Fetch()) - { - return record?.GetString(1); - } - } - } - - private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value; - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs deleted file mode 100644 index af4ab3a8..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Processes the Msu packages to add properties and payloads from the Msu packages. - /// - internal class ProcessMsuPackageCommand - { - public ProcessMsuPackageCommand(PackageFacade facade, Dictionary payloadSymbols) - { - this.AuthoredPayloads = payloadSymbols; - this.Facade = facade; - } - - public Dictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public void Execute() - { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = packagePayload.Hash; - } - - this.Facade.PackageSymbol.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 deleted file mode 100644 index fa70251a..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.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.Core.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ProcessPayloadsCommand - { - public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) - { - this.BackendHelper = backendHelper; - this.PayloadHarvester = payloadHarvester; - this.Payloads = payloads; - this.DefaultPackaging = defaultPackaging; - this.LayoutDirectory = layoutDirectory; - } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable TrackedFiles { get; private set; } - - private IBackendHelper BackendHelper { get; } - - private IPayloadHarvester PayloadHarvester { get; } - - private IEnumerable Payloads { get; } - - private PackagingType DefaultPackaging { get; } - - private string LayoutDirectory { get; } - - public void Execute() - { - var fileTransfers = new List(); - var trackedFiles = new List(); - - foreach (var payload in this.Payloads) - { - payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name); - - // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden - // in the .wixlib). - var sourceFile = payload.SourceFile; - payload.ContentFile = sourceFile != null && !sourceFile.Embed; - - this.UpdatePayloadPackagingType(payload); - - if (!this.PayloadHarvester.HarvestStandardInformation(payload)) - { - // 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. - { - // External payloads need to be transfered. - if (PackagingType.External == payload.Packaging) - { - var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers); - fileTransfers.Add(transfer); - } - - if (payload.ContentFile) - { - trackedFiles.Add(this.BackendHelper.TrackFile(sourceFile.Path, TrackedFileType.Input, payload.SourceLineNumbers)); - } - } - } - - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - } - - private void UpdatePayloadPackagingType(WixBundlePayloadSymbol payload) - { - if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging) - { - if (!payload.Compressed.HasValue) - { - payload.Packaging = this.DefaultPackaging; - } - else if (payload.Compressed.Value) - { - payload.Packaging = PackagingType.Embedded; - } - else - { - payload.Packaging = PackagingType.External; - } - } - - // Embedded payloads that are not assigned a container already are placed in the default attached - // container. - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef)) - { - payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; - } - } - } -} diff --git a/src/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/WixToolset.Core.Burn/BurnBackendErrors.cs deleted file mode 100644 index 854c84e0..00000000 --- a/src/WixToolset.Core.Burn/BurnBackendErrors.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using WixToolset.Data; - - internal static class BurnBackendErrors - { - public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) - { - return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the BA container. When extracting the container at runtime, the file will get overwritten.", payloadId, payloadName); - } - - public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error."); - } - - public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId) - { - return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId); - } - - public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber) - { - return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error."); - } - - public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName) - { - return Message(sourceLineNumbers, Ids.ExternalPayloadCollision, "The external {0} '{1}' has a duplicate Name '{2}'. When building the bundle or laying out the bundle, the file will get overwritten.", symbolName, payloadId, payloadName); - } - - public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error."); - } - - public static Message MultipleAttachedContainersUnsupported(SourceLineNumber sourceLineNumbers, string containerId) - { - return Message(sourceLineNumbers, Ids.MultipleAttachedContainersUnsupported, "Bundles don't currently support having more than one attached container. Either remove all authored attached containers to use the default attached container, or make sure all compressed payloads are included in this Container '{0}'.", containerId); - } - - public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId) - { - return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in package '{2}'. When caching the package, the file will get overwritten.", payloadId, payloadName, packageId); - } - - public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error."); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - DuplicateCacheIds = 8000, - DuplicateCacheIds2 = 8001, - BAContainerPayloadCollision = 8002, - BAContainerPayloadCollision2 = 8003, - ExternalPayloadCollision = 8004, - ExternalPayloadCollision2 = 8005, - PackageCachePayloadCollision = 8006, - PackageCachePayloadCollision2 = 8007, - MultipleAttachedContainersUnsupported = 8008, - } // last available is 8499. 8500 is BurnBackendWarnings. - } -} diff --git a/src/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/WixToolset.Core.Burn/BurnBackendFactory.cs deleted file mode 100644 index 03013a08..00000000 --- a/src/WixToolset.Core.Burn/BurnBackendFactory.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.Core.Burn -{ - using System; - using System.IO; - using WixToolset.Extensibility; - - internal class BurnBackendFactory : IBackendFactory - { - public bool TryCreateBackend(string outputType, string outputFile, 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/BurnBackendWarnings.cs b/src/WixToolset.Core.Burn/BurnBackendWarnings.cs deleted file mode 100644 index a0ffa1dc..00000000 --- a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using WixToolset.Data; - - internal static class BurnBackendWarnings - { - public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) - { - return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", payloadId, payloadName); - } - - public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error."); - } - - public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string containerId) - { - return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - AttachedContainerPayloadCollision = 8500, - AttachedContainerPayloadCollision2 = 8501, - EmptyContainer = 8502, - } // last available is 8999. 9000 is VerboseMessages. - } -} diff --git a/src/WixToolset.Core.Burn/BurnExtensionFactory.cs b/src/WixToolset.Core.Burn/BurnExtensionFactory.cs deleted file mode 100644 index b34d12c1..00000000 --- a/src/WixToolset.Core.Burn/BurnExtensionFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System; - using WixToolset.Extensibility; - - internal class BurnExtensionFactory : IExtensionFactory - { - public bool TryCreateExtension(Type extensionType, out object extension) - { - extension = null; - - if (extensionType == typeof(IBackendFactory)) - { - extension = new BurnBackendFactory(); - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs deleted file mode 100644 index e4d2b0c9..00000000 --- a/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BurnBackendHelper : IInternalBurnBackendHelper - { - public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; - public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; - - private readonly IBackendHelper backendHelper; - - private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); - - private Dictionary BundleExtensionDataById { get; } = new Dictionary(); - - public BurnBackendHelper(IServiceProvider serviceProvider) - { - this.backendHelper = serviceProvider.GetService(); - } - - #region IBackendHelper interfaces - - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); - - public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); - - public string CreateGuid() => this.backendHelper.CreateGuid(); - - public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); - - public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); - - public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); - - public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); - - public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); - - public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); - - public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); - - public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); - - public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); - - public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); - - public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); - - public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); - - public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); - - #endregion - - #region IBurnBackendHelper interfaces - - public void AddBootstrapperApplicationData(string xml) - { - this.BootstrapperApplicationManifestData.AddXml(xml); - } - - public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) - { - this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace); - } - - public void AddBundleExtensionData(string extensionId, string xml) - { - var manifestData = this.GetBundleExtensionManifestData(extensionId); - manifestData.AddXml(xml); - } - - public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) - { - var manifestData = this.GetBundleExtensionManifestData(extensionId); - manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); - } - - #endregion - - #region IInternalBurnBackendHelper interfaces - - public void WriteBootstrapperApplicationData(XmlWriter writer) - { - this.BootstrapperApplicationManifestData.Write(writer); - } - - public void WriteBundleExtensionData(XmlWriter writer) - { - foreach (var kvp in this.BundleExtensionDataById) - { - this.WriteExtension(writer, kvp.Key, kvp.Value); - } - } - - #endregion - - private ManifestData GetBundleExtensionManifestData(string extensionId) - { - if (!this.backendHelper.IsValidIdentifier(extensionId)) - { - throw new ArgumentException($"'{extensionId}' is not a valid extensionId"); - } - - if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData)) - { - manifestData = new ManifestData(); - this.BundleExtensionDataById.Add(extensionId, manifestData); - } - - return manifestData; - } - - private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData) - { - writer.WriteStartElement("BundleExtension"); - - writer.WriteAttributeString("Id", extensionId); - - manifestData.Write(writer); - - writer.WriteEndElement(); - } - - private class ManifestData - { - public ManifestData() - { - this.Builder = new StringBuilder(); - } - - private StringBuilder Builder { get; } - - public void AddSymbol(IntermediateSymbol symbol, bool symbolIdIsIdAttribute, string ns) - { - // There might be a more efficient way to do this, - // but this is an easy way to ensure we're creating valid XML. - var sb = new StringBuilder(); - using (var writer = XmlWriter.Create(sb, WriterSettings)) - { - writer.WriteStartElement(symbol.Definition.Name, ns); - - if (symbolIdIsIdAttribute && symbol.Id != null) - { - writer.WriteAttributeString("Id", symbol.Id.Id); - } - - foreach (var field in symbol.Fields) - { - if (!field.IsNull()) - { - writer.WriteAttributeString(field.Definition.Name, field.AsString()); - } - } - - writer.WriteEndElement(); - } - - this.AddXml(sb.ToString()); - } - - public void AddXml(string xml) - { - // There might be a more efficient way to do this, - // but this is an easy way to ensure we're given valid XML. - var sb = new StringBuilder(); - using (var xmlWriter = XmlWriter.Create(sb, WriterSettings)) - { - AddManifestDataFromString(xmlWriter, xml); - } - this.Builder.Append(sb.ToString()); - } - - public void Write(XmlWriter writer) - { - AddManifestDataFromString(writer, this.Builder.ToString()); - } - - private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml) - { - using (var stringReader = new StringReader(xml)) - using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings)) - { - while (xmlReader.MoveToContent() != XmlNodeType.None) - { - xmlWriter.WriteNode(xmlReader, false); - } - } - } - } - } -} diff --git a/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs b/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs deleted file mode 100644 index 9ef91028..00000000 --- a/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.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.Core.Burn.ExtensibilityServices -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Data.Symbols; - - internal class PayloadHarvester : IPayloadHarvester - { - private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); - - /// - public bool HarvestStandardInformation(WixBundlePayloadSymbol payload) - { - var filePath = payload.SourceFile?.Path; - - if (String.IsNullOrEmpty(filePath)) - { - return false; - } - - this.UpdatePayloadFileInformation(payload, filePath); - - this.UpdatePayloadVersionInformation(payload, filePath); - - return true; - } - - private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath) - { - var fileInfo = new FileInfo(filePath); - - if (null != fileInfo) - { - payload.FileSize = fileInfo.Length; - - payload.Hash = BundleHashAlgorithm.Hash(fileInfo); - } - else - { - payload.FileSize = 0; - } - } - - private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath) - { - var versionInfo = FileVersionInfo.GetVersionInfo(filePath); - - if (null != versionInfo) - { - // Use the fixed version info block for the file since the resource text may not be a dotted quad. - var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); - - if (PayloadHarvester.EmptyVersion != version) - { - payload.Version = version.ToString(); - } - - payload.Description = versionInfo.FileDescription; - payload.DisplayName = versionInfo.ProductName; - } - } - } -} diff --git a/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs b/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs deleted file mode 100644 index 59c4f20f..00000000 --- a/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System.Xml; - using WixToolset.Extensibility.Services; - - internal interface IInternalBurnBackendHelper : IBurnBackendHelper - { - void WriteBootstrapperApplicationData(XmlWriter writer); - - void WriteBundleExtensionData(XmlWriter writer); - } -} diff --git a/src/WixToolset.Core.Burn/ISearchFacade.cs b/src/WixToolset.Core.Burn/ISearchFacade.cs deleted file mode 100644 index b9ad8649..00000000 --- a/src/WixToolset.Core.Burn/ISearchFacade.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn -{ - using System.Xml; - - internal interface ISearchFacade - { - /// - /// Writes the search to the Burn manifest. - /// - /// - void WriteXml(XmlTextWriter writer); - } -} diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs deleted file mode 100644 index b466d0de..00000000 --- a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.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.Core.Burn.Inscribe -{ - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Native; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class InscribeBundleCommand - { - public InscribeBundleCommand(IInscribeContext context) - { - this.Context = context; - - this.Messaging = context.ServiceProvider.GetService(); - } - - private IInscribeContext Context { get; } - - public IMessaging Messaging { get; } - - public bool Execute() - { - var inscribed = false; - var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); - - using (var reader = BurnReader.Open(this.Context.InputFilePath)) - { - FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false); - - // 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 (var writer = BurnWriter.Open(this.Messaging, tempFile)) - { - writer.RememberThenResetSignature(); - writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); - inscribed = true; - } - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); - - FileSystem.MoveFile(tempFile, this.Context.OutputFile); - - return inscribed; - } - } -} diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs deleted file mode 100644 index a6789796..00000000 --- a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Inscribe -{ - using System; - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Native; - using WixToolset.Extensibility.Data; - - internal class InscribeBundleEngineCommand - { - public InscribeBundleEngineCommand(IInscribeContext context) - { - this.IntermediateFolder = context.IntermediateFolder; - this.InputFilePath = context.InputFilePath; - this.OutputFile = context.OutputFile; - } - - private string IntermediateFolder { get; } - - private string InputFilePath { get; } - - private string OutputFile { get; } - - public bool Execute() - { - var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); - - using (var reader = BurnReader.Open(this.InputFilePath)) - using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) - { - reader.Stream.Seek(0, SeekOrigin.Begin); - - var buffer = new byte[4 * 1024]; - var total = 0; - var 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.OutputFile)); - - FileSystem.MoveFile(tempFile, this.OutputFile); - - return true; - } - } -} diff --git a/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs b/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs deleted file mode 100644 index 1bafa46e..00000000 --- a/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Burn.Interfaces -{ - using System.Diagnostics; - using WixToolset.Data.Symbols; - - /// - /// Service for harvesting payload information. - /// - public interface IPayloadHarvester - { - /// - /// Uses to: - /// update from file contents, - /// update from file size, and - /// update , , and from . - /// - /// The symbol to update. - /// Whether the symbol had a source file specified. - bool HarvestStandardInformation(WixBundlePayloadSymbol payload); - } -} diff --git a/src/WixToolset.Core.Burn/RowIndexedList.cs b/src/WixToolset.Core.Burn/RowIndexedList.cs deleted file mode 100644 index fd762a24..00000000 --- a/src/WixToolset.Core.Burn/RowIndexedList.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.Core.Burn -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.WindowsInstaller; - - /// - /// A list of rows indexed by their primary key. Unlike a RowDictionary - /// this indexed list will track rows in their added order and will allow rows with - /// duplicate keys to be added to the list, although only the first row will be indexed. - /// - internal sealed class RowIndexedList : IList where T : Row - { - private readonly Dictionary index; - private readonly List rows; - private readonly List duplicates; - - /// - /// Creates an empty . - /// - public RowIndexedList() - { - this.index = new Dictionary(StringComparer.InvariantCulture); - this.rows = new List(); - this.duplicates = new List(); - } - - /// - /// Creates and populates a with the rows from the given enumerator. - /// - /// Rows to index. - public RowIndexedList(IEnumerable rows) - : this() - { - foreach (var row in rows) - { - this.Add(row); - } - } - - /// - /// Creates and populates a with the rows from the given . - /// - /// The table to index. - /// - /// Rows added to the index are not automatically added to the given . - /// - public RowIndexedList(Table table) - : this() - { - if (null != table) - { - foreach (T row in table.Rows) - { - this.Add(row); - } - } - } - - /// - /// Gets the duplicates in the list. - /// - public IEnumerable Duplicates { get { return this.duplicates; } } - - /// - /// Gets the row by integer key. - /// - /// Integer key to look up. - /// Row or null if key is not found. - public T Get(int key) - { - return this.Get(key.ToString()); - } - - /// - /// Gets the row by string key. - /// - /// String key to look up. - /// Row or null if key is not found. - public T Get(string key) - { - return this.TryGet(key, out var result) ? result : null; - } - - /// - /// Gets the row by string key if it exists. - /// - /// Key of row to get. - /// Row found. - /// True if key was found otherwise false. - public bool TryGet(string key, out T row) - { - return this.index.TryGetValue(key, out row); - } - - /// - /// Tries to add a row as long as it would not create a duplicate. - /// - /// Row to add. - /// True if the row as added otherwise false. - public bool TryAdd(T row) - { - try - { - this.index.Add(row.GetKey(), row); - } - catch (ArgumentException) // if the key already exists, bail. - { - return false; - } - - this.rows.Add(row); - return true; - } - - /// - /// Adds a row to the list. If a row with the same key is already index, the row is - /// is not in the index but will still be part of the list and added to the duplicates - /// list. - /// - /// - public void Add(T row) - { - this.rows.Add(row); - try - { - this.index.Add(row.GetKey(), row); - } - catch (ArgumentException) // if the key already exists, we have a duplicate. - { - this.duplicates.Add(row); - } - } - - /// - /// Gets the index of a row. - /// - /// Iterates through the list of rows to find the index of a particular row. - /// Index of row or -1 if not found. - public int IndexOf(T row) - { - return this.rows.IndexOf(row); - } - - /// - /// Inserts a row at a particular index of the list. - /// - /// Index to insert the row after. - /// Row to insert. - public void Insert(int index, T row) - { - this.rows.Insert(index, row); - try - { - this.index.Add(row.GetKey(), row); - } - catch (ArgumentException) // if the key already exists, we have a duplicate. - { - this.duplicates.Add(row); - } - } - - /// - /// Removes a row from a particular index. - /// - /// Index to remove the row at. - public void RemoveAt(int index) - { - var row = this.rows[index]; - - this.rows.RemoveAt(index); - - if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) - { - this.index.Remove(row.GetKey()); - } - else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). - { - this.duplicates.Remove(row); - } - } - - /// - /// Gets or sets a row at the specified index. - /// - /// Index to get the row. - /// Row at specified index. - public T this[int index] - { - get - { - return this.rows[index]; - } - set - { - this.rows[index] = value; - try - { - this.index.Add(value.GetKey(), value); - } - catch (ArgumentException) // if the key already exists, we have a duplicate. - { - this.duplicates.Add(value); - } - } - } - - /// - /// Empties the list and it's index. - /// - public void Clear() - { - this.index.Clear(); - this.rows.Clear(); - this.duplicates.Clear(); - } - - /// - /// Searches the list for a row without using the index. - /// - /// Row to look for in the list. - /// True if the row is in the list, otherwise false. - public bool Contains(T row) - { - return this.rows.Contains(row); - } - - /// - /// Copies the rows of the list to an array. - /// - /// Array to copy the list into. - /// Index to start copying at. - public void CopyTo(T[] array, int arrayIndex) - { - this.rows.CopyTo(array, arrayIndex); - } - - /// - /// Number of rows in the list. - /// - public int Count - { - get { return this.rows.Count; } - } - - /// - /// Indicates whether the list is read-only. Always false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Removes a row from the list. Indexed rows will be removed but the colleciton will NOT - /// promote duplicates to the index automatically. The duplicate would also need to be removed - /// and re-added to be indexed. - /// - /// - /// - public bool Remove(T row) - { - var removed = this.rows.Remove(row); - if (removed) - { - if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) - { - this.index.Remove(row.GetKey()); - } - else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). - { - this.duplicates.Remove(row); - } - } - - return removed; - } - - /// - /// Gets an enumerator over the whole list. - /// - /// List enumerator. - public IEnumerator GetEnumerator() - { - return this.rows.GetEnumerator(); - } - - /// - /// Gets an untyped enumerator over the whole list. - /// - /// Untyped list enumerator. - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return this.rows.GetEnumerator(); - } - } -} diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj deleted file mode 100644 index f2da8a50..00000000 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Core Burn - WiX Toolset Core Burn - embedded - true - true - - - - - <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs deleted file mode 100644 index 58076d5e..00000000 --- a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.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.Core.Burn -{ - using System; - using System.Collections.Generic; - using WixToolset.Core.Burn.ExtensibilityServices; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Extensibility.Services; - - /// - /// Extensions methods for adding Burn services. - /// - public static class WixToolsetCoreServiceProviderExtensions - { - /// - /// Adds Burn Services. - /// - /// - /// - public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) - { - AddServices(coreProvider); - - var extensionManager = coreProvider.GetService(); - extensionManager.Add(typeof(BurnExtensionFactory).Assembly); - - return coreProvider; - } - - private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) - { - // Singletons. - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new BurnBackendHelper(provider))); - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new PayloadHarvester())); - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, provider.GetService())); - } - - private static T AddSingleton(Dictionary singletons, T service) where T : class - { - singletons.Add(typeof(T), service); - return service; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/CachedExtension.cs b/src/WixToolset.Core.ExtensionCache/CachedExtension.cs deleted file mode 100644 index 5567541c..00000000 --- a/src/WixToolset.Core.ExtensionCache/CachedExtension.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensionCache -{ - internal class CachedExtension - { - public CachedExtension(string id, string version, bool damaged) - { - this.Id = id; - this.Version = version; - this.Damaged = damaged; - } - - public string Id { get; } - - public string Version { get; } - - public bool Damaged { get; } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs deleted file mode 100644 index 256eeb0b..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensionCache -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using NuGet.Common; - using NuGet.Configuration; - using NuGet.Credentials; - using NuGet.Packaging; - using NuGet.Protocol; - using NuGet.Protocol.Core.Types; - using NuGet.Versioning; - - /// - /// Extension cache manager. - /// - internal class ExtensionCacheManager - { - public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder(); - - public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions"); - - public string GlobalCacheFolder() - { - var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - return Path.Combine(baseFolder, ".wix", "extensions"); - } - - public async Task AddAsync(bool global, string extension, CancellationToken cancellationToken) - { - if (String.IsNullOrEmpty(extension)) - { - throw new ArgumentNullException(nameof(extension)); - } - - (var extensionId, var extensionVersion) = ParseExtensionReference(extension); - - var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken); - - return result; - } - - public Task RemoveAsync(bool global, string extension, CancellationToken cancellationToken) - { - if (String.IsNullOrEmpty(extension)) - { - throw new ArgumentNullException(nameof(extension)); - } - - (var extensionId, var extensionVersion) = ParseExtensionReference(extension); - - var cacheFolder = this.CacheFolder(global); - - cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); - - if (Directory.Exists(cacheFolder)) - { - cancellationToken.ThrowIfCancellationRequested(); - - Directory.Delete(cacheFolder, true); - return Task.FromResult(true); - } - - return Task.FromResult(false); - } - - public Task> ListAsync(bool global, string extension, CancellationToken cancellationToken) - { - var found = new List(); - - (var extensionId, var extensionVersion) = ParseExtensionReference(extension); - - var cacheFolder = this.CacheFolder(global); - - var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); - - if (!Directory.Exists(searchFolder)) - { - } - else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension. - { - var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion); - found.Add(new CachedExtension(extensionId, extensionVersion, !present)); - } - else // looking for all versions of an extension or all versions of all extensions. - { - IEnumerable foundExtensionIds; - - if (String.IsNullOrEmpty(extensionId)) - { - // Looking for all versions of all extensions. - foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList(); - } - else - { - // Looking for all versions of a single extension. - var extensionFolder = Path.Combine(cacheFolder, extensionId); - foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty(); - } - - foreach (var foundExtensionId in foundExtensionIds) - { - var extensionFolder = Path.Combine(cacheFolder, foundExtensionId); - - foreach (var folder in Directory.GetDirectories(extensionFolder)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var foundExtensionVersion = Path.GetFileName(folder); - - if (!NuGetVersion.TryParse(foundExtensionVersion, out _)) - { - continue; - } - - var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion); - found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present)); - } - } - } - - return Task.FromResult((IEnumerable)found); - } - - private async Task DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken) - { - var logger = NullLogger.Instance; - - DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false); - - var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory); - var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled); - - using (var cache = new SourceCacheContext()) - { - PackageSource versionSource = null; - - var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version); - - if (nugetVersion is null) - { - foreach (var source in sources) - { - var repository = Repository.Factory.GetCoreV3(source.Source); - var resource = await repository.GetResourceAsync(); - - var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken); - foreach (var availableVersion in availableVersions) - { - if (nugetVersion is null || nugetVersion < availableVersion) - { - nugetVersion = availableVersion; - versionSource = source; - } - } - } - - if (nugetVersion is null) - { - return false; - } - } - - var searchSources = versionSource is null ? sources : new[] { versionSource }; - - var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString()); - - foreach (var source in searchSources) - { - var repository = Repository.Factory.GetCoreV3(source.Source); - var resource = await repository.GetResourceAsync(); - - using (var stream = new MemoryStream()) - { - var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken); - - if (downloaded) - { - stream.Position = 0; - - using (var archive = new PackageArchiveReader(stream)) - { - var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase); - await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken); - } - - return true; - } - } - } - } - - return false; - } - - private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath); - - private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference) - { - var extensionId = extensionReference ?? String.Empty; - var extensionVersion = String.Empty; - - var index = extensionId.LastIndexOf('/'); - if (index > 0) - { - extensionVersion = extensionReference.Substring(index + 1); - extensionId = extensionReference.Substring(0, index); - - if (!NuGetVersion.TryParse(extensionVersion, out _)) - { - throw new ArgumentException($"Invalid extension version in {extensionReference}"); - } - - if (String.IsNullOrEmpty(extensionId)) - { - throw new ArgumentException($"Invalid extension id in {extensionReference}"); - } - } - - return (extensionId, extensionVersion); - } - - private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion) - { - var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools"); - if (!Directory.Exists(toolsFolder)) - { - return false; - } - - var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll"); - - var present = File.Exists(extensionAssembly); - if (!present) - { - extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe"); - present = File.Exists(extensionAssembly); - } - - return present; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs deleted file mode 100644 index 94ee4f22..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensionCache -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Extension cache manager command. - /// - internal class ExtensionCacheManagerCommand : ICommandLineCommand - { - private enum CacheSubcommand - { - Add, - Remove, - List - } - - public ExtensionCacheManagerCommand(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - this.ExtensionReferences = new List(); - } - - private IMessaging Messaging { get; } - - public bool ShowLogo { get; private set; } - - public bool StopParsing { get; private set; } - - private bool ShowHelp { get; set; } - - private bool Global { get; set; } - - private CacheSubcommand? Subcommand { get; set; } - - private List ExtensionReferences { get; } - - public async Task ExecuteAsync(CancellationToken cancellationToken) - { - if (this.ShowHelp || !this.Subcommand.HasValue) - { - DisplayHelp(); - return 1; - } - - var success = false; - var cacheManager = new ExtensionCacheManager(); - - switch (this.Subcommand) - { - case CacheSubcommand.Add: - success = await this.AddExtensions(cacheManager, cancellationToken); - break; - - case CacheSubcommand.Remove: - success = await this.RemoveExtensions(cacheManager, cancellationToken); - break; - - case CacheSubcommand.List: - success = await this.ListExtensions(cacheManager, cancellationToken); - break; - } - - return success ? 0 : 2; - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - if (!parser.IsSwitch(argument)) - { - if (!this.Subcommand.HasValue) - { - if (!Enum.TryParse(argument, true, out CacheSubcommand subcommand)) - { - return false; - } - - this.Subcommand = subcommand; - } - else - { - this.ExtensionReferences.Add(argument); - } - - return true; - } - - var parameter = argument.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "-help": - this.ShowHelp = true; - this.ShowLogo = true; - this.StopParsing = true; - return true; - - case "nologo": - case "-nologo": - this.ShowLogo = false; - return true; - - case "g": - case "-global": - this.Global = true; - return true; - } - - return false; - } - - private async Task AddExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) - { - var success = false; - - foreach (var extensionRef in this.ExtensionReferences) - { - var added = await cacheManager.AddAsync(this.Global, extensionRef, cancellationToken); - success |= added; - } - - return success; - } - - private async Task RemoveExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) - { - var success = false; - - foreach (var extensionRef in this.ExtensionReferences) - { - var removed = await cacheManager.RemoveAsync(this.Global, extensionRef, cancellationToken); - success |= removed; - } - - return success; - } - - private async Task ListExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) - { - var found = false; - var extensionRef = this.ExtensionReferences.FirstOrDefault(); - - var extensions = await cacheManager.ListAsync(this.Global, extensionRef, cancellationToken); - - foreach (var extension in extensions) - { - this.Messaging.Write($"{extension.Id} {extension.Version}{(extension.Damaged ? " (damaged)" : String.Empty)}"); - found = true; - } - - return found; - } - - private static void DisplayHelp() - { - Console.WriteLine(); - Console.WriteLine("Usage: wix extension add|remove|list [extensionRef]"); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine(" -h|--help Show command line help."); - Console.WriteLine(" -g|--global Add/remove the extension for the current user."); - Console.WriteLine(" --nologo Suppress displaying the logo information."); - Console.WriteLine(); - Console.WriteLine("Commands:"); - Console.WriteLine(); - Console.WriteLine(" add Add extension to the cache."); - Console.WriteLine(" list List extensions in the cache."); - Console.WriteLine(" remove Remove extension from the cache."); - Console.WriteLine(); - Console.WriteLine(" extensionRef format: extensionId/version (the version is optional)"); - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs deleted file mode 100644 index 2a603adf..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensionCache -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Parses the "extension" command-line command. See ExtensionCacheManagerCommand - /// for the bulk of the command-line processing. - /// - internal class ExtensionCacheManagerExtensionCommandLine : BaseExtensionCommandLine - { - public ExtensionCacheManagerExtensionCommandLine(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IServiceProvider ServiceProvider { get; } - - public override IReadOnlyCollection CommandLineSwitches => new ExtensionCommandLineSwitch[] - { - new ExtensionCommandLineSwitch { Switch = "extension", Description = "Manage extension cache." }, - }; - - public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) - { - command = null; - - if ("extension".Equals(argument, StringComparison.OrdinalIgnoreCase)) - { - command = new ExtensionCacheManagerCommand(this.ServiceProvider); - } - - return command != null; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs deleted file mode 100644 index c38e5c70..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.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.Core.ExtensionCache -{ - using System; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ExtensionCacheManagerExtensionFactory : IExtensionFactory - { - public ExtensionCacheManagerExtensionFactory(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IServiceProvider ServiceProvider { get; } - - public bool TryCreateExtension(Type extensionType, out object extension) - { - extension = null; - - if (extensionType == typeof(IExtensionCommandLine)) - { - extension = new ExtensionCacheManagerExtensionCommandLine(this.ServiceProvider); - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj b/src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj deleted file mode 100644 index 1383305c..00000000 --- a/src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Extension Cache - WiX Toolset Extension Cache - embedded - true - true - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs deleted file mode 100644 index 424fc469..00000000 --- a/src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensionCache -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility.Services; - - /// - /// Extensions methods for adding ExtensionCache services. - /// - public static class WixToolsetCoreServiceProviderExtensions - { - /// - /// Adds ExtensionCache services. - /// - /// - /// - public static IWixToolsetCoreServiceProvider AddExtensionCacheManager(this IWixToolsetCoreServiceProvider coreProvider) - { - var extensionManager = coreProvider.GetService(); - extensionManager.Add(typeof(ExtensionCacheManagerExtensionFactory).Assembly); - - coreProvider.AddService(CreateExtensionCacheManager); - return coreProvider; - } - - private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary singletons) - { - var extensionCacheManager = new ExtensionCacheManager(); - singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager); - - return extensionCacheManager; - } - } -} diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs deleted file mode 100644 index 8c9f31e6..00000000 --- a/src/WixToolset.Core.TestPackage/BundleExtractor.cs +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.TestPackage -{ - using System.IO; - using System.Xml; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Extensibility.Services; - - /// - /// Class to extract bundle contents for testing. - /// - public class BundleExtractor - { - /// - /// Extracts the BA container. - /// - /// - /// Path to the bundle. - /// Path to extract to. - /// Temp path for extraction. - /// - public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) - { - var result = new ExtractBAContainerResult(); - Directory.CreateDirectory(tempFolderPath); - using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) - { - result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); - } - - if (result.Success) - { - result.ManifestDocument = LoadBurnManifest(destinationFolderPath); - result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); - - result.BADataDocument = LoadBAData(destinationFolderPath); - result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); - - result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); - result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); - } - - return result; - } - - /// - /// Extracts the attached container. - /// - /// - /// Path to the bundle. - /// Path to extract to. - /// Temp path for extraction. - /// True if there was an attached container. - public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) - { - Directory.CreateDirectory(tempFolderPath); - using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) - { - return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath); - } - } - - /// - /// Gets an for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace. - /// - /// - /// - /// - public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); - return namespaceManager; - } - - /// - /// Gets an for BundleExtensionData.xml with the given prefix assigned to the root namespace. - /// - /// - /// - /// - public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); - return namespaceManager; - } - - /// - /// Gets an for the Burn manifest.xml with the given prefix assigned to the root namespace. - /// - /// - /// - /// - public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); - return namespaceManager; - } - - /// - /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. - /// - /// - /// - public static XmlDocument LoadBAData(string baFolderPath) - { - var document = new XmlDocument(); - document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); - return document; - } - - /// - /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. - /// - /// - /// - public static XmlDocument LoadBundleExtensionData(string baFolderPath) - { - var document = new XmlDocument(); - document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); - return document; - } - - /// - /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. - /// - /// - /// - public static XmlDocument LoadBurnManifest(string baFolderPath) - { - var document = new XmlDocument(); - document.Load(Path.Combine(baFolderPath, "manifest.xml")); - return document; - } - } -} diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs deleted file mode 100644 index 277861ff..00000000 --- a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.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.Core.TestPackage -{ - using System.IO; - using System.Xml; - using Xunit; - - /// - /// The result of extracting the BA container. - /// - public class ExtractBAContainerResult - { - /// - /// for BundleExtensionData.xml. - /// - public XmlDocument BundleExtensionDataDocument { get; set; } - - /// - /// for BundleExtensionData.xml. - /// - public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } - - /// - /// for BootstrapperApplicationData.xml. - /// - public XmlDocument BADataDocument { get; set; } - - /// - /// for BootstrapperApplicationData.xml. - /// - public XmlNamespaceManager BADataNamespaceManager { get; set; } - - /// - /// for the Burn manifest.xml. - /// - public XmlDocument ManifestDocument { get; set; } - - /// - /// for the Burn manifest.xml. - /// - public XmlNamespaceManager ManifestNamespaceManager { get; set; } - - /// - /// Whether extraction succeeded. - /// - public bool Success { get; set; } - - /// - /// - /// - /// - public ExtractBAContainerResult AssertSuccess() - { - Assert.True(this.Success); - return this; - } - - /// - /// Returns the relative path of the BA entry point dll in the given folder. - /// - /// - /// - public string GetBAFilePath(string extractedBAContainerFolderPath) - { - var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); - var baPayload = uxPayloads[0]; - var relativeBAPath = baPayload.Attributes["FilePath"].Value; - return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); - } - - /// - /// Returns the relative path of the BundleExtension entry point dll in the given folder. - /// - /// - /// - /// - public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) - { - var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); - var bextPayload = uxPayloads[0]; - var relativeBextPath = bextPayload.Attributes["FilePath"].Value; - return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); - } - - /// - /// - /// - /// elements must have the 'ba' prefix - /// - public XmlNodeList SelectBADataNodes(string xpath) - { - return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); - } - - /// - /// - /// - /// elements must have the 'be' prefix - /// - public XmlNodeList SelectBundleExtensionDataNodes(string xpath) - { - return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); - } - - /// - /// - /// - /// elements must have the 'burn' prefix - /// - public XmlNodeList SelectManifestNodes(string xpath) - { - return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); - } - } -} diff --git a/src/WixToolset.Core.TestPackage/TestMessageListener.cs b/src/WixToolset.Core.TestPackage/TestMessageListener.cs deleted file mode 100644 index 7040fe82..00000000 --- a/src/WixToolset.Core.TestPackage/TestMessageListener.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using WixToolset.Data; -using WixToolset.Extensibility; -using WixToolset.Extensibility.Services; - -namespace WixToolset.Core.TestPackage -{ - /// - /// An that simply stores all the messages. - /// - public sealed class TestMessageListener : IMessageListener - { - /// - /// All messages that have been received. - /// - public List Messages { get; } = new List(); - - /// - /// - /// - public string ShortAppName => "TEST"; - - /// - /// - /// - public string LongAppName => "Test"; - - /// - /// Stores the message in . - /// - /// - public void Write(Message message) - { - this.Messages.Add(message); - } - - /// - /// Stores the message in . - /// - /// - public void Write(string message) - { - this.Messages.Add(new Message(null, MessageLevel.Information, 0, message)); - } - - /// - /// Always returns defaultMessageLevel. - /// - /// - /// - /// - /// - public MessageLevel CalculateMessageLevel(IMessaging messaging, Message message, MessageLevel defaultMessageLevel) => defaultMessageLevel; - } -} diff --git a/src/WixToolset.Core.TestPackage/WixRunner.cs b/src/WixToolset.Core.TestPackage/WixRunner.cs deleted file mode 100644 index ed7c49b8..00000000 --- a/src/WixToolset.Core.TestPackage/WixRunner.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.TestPackage -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Core.Burn; - using WixToolset.Core.WindowsInstaller; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// Utility class to emulate wix.exe with standard backends. - /// - public static class WixRunner - { - /// - /// Emulates calling wix.exe with standard backends. - /// - /// - /// - /// - /// - public static int Execute(string[] args, out List messages, bool warningsAsErrors = true) - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var task = Execute(args, serviceProvider, out messages, warningsAsErrors: warningsAsErrors); - return task.Result; - } - - /// - /// Emulates calling wix.exe with standard backends. - /// This overload always treats warnings as errors. - /// - /// - /// - public static WixRunnerResult Execute(params string[] args) - { - return Execute(true, args); - } - - /// - /// Emulates calling wix.exe with standard backends. - /// - /// - /// - /// - public static WixRunnerResult Execute(bool warningsAsErrors, params string[] args) - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var exitCode = Execute(args, serviceProvider, out var messages, warningsAsErrors: warningsAsErrors); - return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() }; - } - - /// - /// Emulates calling wix.exe with standard backends. - /// - /// - /// - /// - /// - /// - public static Task Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List messages, bool warningsAsErrors = true) - { - coreProvider.AddWindowsInstallerBackend() - .AddBundleBackend(); - - var listener = new TestMessageListener(); - - messages = listener.Messages; - - var messaging = coreProvider.GetService(); - messaging.SetListener(listener); - - var arguments = new List(args); - if (warningsAsErrors) - { - arguments.Add("-wx"); - } - - var commandLine = coreProvider.GetService(); - var command = commandLine.CreateCommand(arguments.ToArray()); - return command?.ExecuteAsync(CancellationToken.None) ?? Task.FromResult(1); - } - } -} diff --git a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs deleted file mode 100644 index 6a3d714c..00000000 --- a/src/WixToolset.Core.TestPackage/WixRunnerResult.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.Core.TestPackage -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using Xunit; - - /// - /// The result of an Execute method of . - /// - public class WixRunnerResult - { - /// - /// ExitCode for the operation. - /// - public int ExitCode { get; set; } - - /// - /// Messages from the operation. - /// - public Message[] Messages { get; set; } - - /// - /// - /// - /// - public WixRunnerResult AssertSuccess() - { - AssertSuccess(this.ExitCode, this.Messages); - return this; - } - - /// - /// - /// - /// - /// - public static void AssertSuccess(int exitCode, IEnumerable messages) - { - Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n"); - } - - private static IEnumerable FormatMessages(IEnumerable messages) - { - foreach (var message in messages) - { - var filename = message.SourceLineNumbers?.FileName ?? "TEST"; - var line = message.SourceLineNumbers?.LineNumber ?? -1; - var type = message.Level.ToString().ToLowerInvariant(); - - if (line > 0) - { - filename = String.Concat(filename, "(", line, ")"); - } - - yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString()); - } - } - } -} diff --git a/src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj b/src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj deleted file mode 100644 index b64b4075..00000000 --- a/src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Internal WiX Toolset Test Package - embedded - true - true - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs deleted file mode 100644 index f4966f74..00000000 --- a/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.TestPackage -{ - using System.Collections.Generic; - using System.IO; - using System.Text.RegularExpressions; - using System.Xml; - - /// - /// Utility class to help compare XML in tests using string comparisons by using single quotes and stripping all namespaces. - /// - public static class XmlNodeExtensions - { - /// - /// Returns the node's outer XML using single quotes and stripping all namespaces. - /// - /// - /// Attributes for which the value should be set to '*'. - /// - public static string GetTestXml(this XmlNode node, Dictionary> ignoredAttributesByElementName = null) - { - return node.OuterXml.GetTestXml(ignoredAttributesByElementName); - } - - /// - /// Returns the XML using single quotes and stripping all namespaces. - /// - /// - /// Attributes for which the value should be set to '*'. - /// - public static string GetTestXml(this string xml, Dictionary> ignoredAttributesByElementName = null) - { - string formattedXml; - using (var sw = new StringWriter()) - using (var writer = new TestXmlWriter(sw)) - { - var doc = new XmlDocument(); - doc.LoadXml(xml); - - if (ignoredAttributesByElementName != null) - { - HandleIgnoredAttributes(doc, ignoredAttributesByElementName); - } - - doc.Save(writer); - formattedXml = sw.ToString(); - } - - return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); - } - - private static void HandleIgnoredAttributes(XmlNode node, Dictionary> ignoredAttributesByElementName) - { - if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) - { - foreach (var ignoredAttribute in ignoredAttributes) - { - var attribute = node.Attributes[ignoredAttribute]; - if (attribute != null) - { - attribute.Value = "*"; - } - } - } - - if (node.ChildNodes != null) - { - foreach (XmlNode childNode in node.ChildNodes) - { - HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); - } - } - } - - private class TestXmlWriter : XmlTextWriter - { - public TestXmlWriter(TextWriter w) - : base(w) - { - this.QuoteChar = '\''; - } - - public override void WriteStartDocument() - { - //OmitXmlDeclaration - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs deleted file mode 100644 index cbba6030..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - - /// - /// Add back possibly suppressed sequence tables since all sequence tables must be present - /// for the merge process to work. We'll drop the suppressed sequence tables again as - /// necessary. - /// - internal class AddBackSuppressedSequenceTablesCommand - { - public AddBackSuppressedSequenceTablesCommand(WindowsInstallerData output, TableDefinitionCollection tableDefinitions) - { - this.Output = output; - this.TableDefinitions = tableDefinitions; - } - - private WindowsInstallerData Output { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - public IEnumerable SuppressedTableNames { get; private set; } - - public IEnumerable Execute() - { - var suppressedTableNames = new HashSet(); - - foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = sequence.WindowsInstallerTableName(); - var sequenceTable = this.Output.Tables[sequenceTableName]; - - if (null == sequenceTable) - { - sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); - } - - if (0 == sequenceTable.Rows.Count) - { - suppressedTableNames.Add(sequenceTableName); - } - } - - return this.SuppressedTableNames = suppressedTableNames; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs deleted file mode 100644 index c4fddb3e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Add CreateFolder symbols, if not already present, for null-keypath components. - /// - internal class AddCreateFoldersCommand - { - internal AddCreateFoldersCommand(IntermediateSection section) - { - this.Section = section; - } - - private IntermediateSection Section { get; } - - public void Execute() - { - var createFolderSymbolsByComponentRef = new HashSet(this.Section.Symbols.OfType().Select(t => t.ComponentRef)); - foreach (var componentSymbol in this.Section.Symbols.OfType().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList()) - { - if (!createFolderSymbolsByComponentRef.Contains(componentSymbol.Id.Id)) - { - this.Section.AddSymbol(new CreateFolderSymbol(componentSymbol.SourceLineNumbers) - { - DirectoryRef = componentSymbol.DirectoryRef, - ComponentRef = componentSymbol.Id.Id, - }); - } - } - } - } -} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs deleted file mode 100644 index ee3bcc91..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - - /// - /// Add referenced standard directory symbols, if not already present. - /// - internal class AddRequiredStandardDirectories - { - internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform) - { - this.Section = section; - this.Platform = platform; - } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - public void Execute() - { - var directories = this.Section.Symbols.OfType().ToList(); - var directoryIds = new SortedSet(directories.Select(d => d.Id.Id)); - - foreach (var directory in directories) - { - var parentDirectoryId = directory.ParentDirectoryRef; - - if (String.IsNullOrEmpty(parentDirectoryId)) - { - if (directory.Id.Id != "TARGETDIR") - { - directory.ParentDirectoryRef = "TARGETDIR"; - } - } - else - { - this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers); - } - } - - if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) - { - directoryIds.Add(targetDir.Id.Id); - this.Section.AddSymbol(targetDir); - } - } - - private void EnsureStandardDirectoryAdded(ISet directoryIds, string directoryId, SourceLineNumber sourceLineNumbers) - { - if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) - { - var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); - - var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id) - { - Name = standardDirectory.Name, - ParentDirectoryRef = parentDirectoryId, - }; - - directoryIds.Add(directory.Id.Id); - this.Section.AddSymbol(directory); - - if (!String.IsNullOrEmpty(parentDirectoryId)) - { - this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers); - } - } - } - - private string GetStandardDirectoryParent(string directoryId) - { - switch (directoryId) - { - case "TARGETDIR": - return null; - - case "CommonFiles6432Folder": - case "ProgramFiles6432Folder": - case "System6432Folder": - return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform); - - default: - return "TARGETDIR"; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs deleted file mode 100644 index 759ba303..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Text; - - internal class AssemblyName - { - public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type) - { - this.Name = name; - this.Culture = culture ?? "neutral"; - this.Version = version; - this.FileVersion = fileVersion; - this.Architecture = architecture; - - this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); - this.PublicKeyToken = publicKeyToken; - this.Type = type; - } - - public string Name { get; } - - public string Culture { get; } - - public string Version { get; } - - public string FileVersion { get; } - - public string Architecture { get; } - - public string PublicKeyToken { get; } - - public bool StrongNamedSigned { get; } - - public string Type { get; } - - public string GetFullName() - { - var assemblyName = new StringBuilder(); - - assemblyName.Append(this.Name); - assemblyName.Append(", Version="); - assemblyName.Append(this.Version); - assemblyName.Append(", Culture="); - assemblyName.Append(this.Culture); - assemblyName.Append(", PublicKeyToken="); - assemblyName.Append(this.PublicKeyToken ?? "null"); - - if (!String.IsNullOrEmpty(this.Architecture)) - { - assemblyName.Append(", ProcessorArchitecture="); - assemblyName.Append(this.Architecture); - } - - return assemblyName.ToString(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs deleted file mode 100644 index 2103cd32..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.IO; - using System.Reflection.Metadata; - using System.Reflection.PortableExecutable; - using System.Security.Cryptography; - using System.Text; - using System.Xml; - using System.Xml.XPath; - using WixToolset.Data; - - internal static class AssemblyNameReader - { - public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion) - { - try - { - using (var stream = File.OpenRead(assemblyPath)) - using (var peReader = new PEReader(stream)) - { - var reader = peReader.GetMetadataReader(); - var headers = peReader.PEHeaders; - - var assembly = reader.GetAssemblyDefinition(); - var attributes = assembly.GetCustomAttributes(); - - var name = ReadString(reader, assembly.Name); - var culture = ReadString(reader, assembly.Culture); - var architecture = ArchitectureFromHeaders(headers); - var version = assembly.Version.ToString(); - var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey); - - // 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. - var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0); - if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length) - { - var versionParts = version.Split('.'); - - if (versionParts.Length > 0) - { - var padding = new string('0', fileVersion.Length - version.Length); - - versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]); - version = String.Join(".", versionParts); - } - } - - return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null); - } - } - catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException) - { - throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}")); - } - } - - public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath) - { - 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 - { - var doc = new XPathDocument(manifestPath); - var 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()) - { - var hasNextSibling = true; - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) - { - hasNextSibling = nav.MoveToNext(); - } - - if (!hasNextSibling) - { - throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath)); - } - - 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) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest")); - } - catch (XmlException xe) - { - throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message)); - } - - return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type); - } - - private static string ArchitectureFromHeaders(PEHeaders headers) - { - if (headers.PEHeader.Magic == PEMagic.PE32Plus) - { - return "AMD64"; - } - else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit) - { - return "x86"; - } - else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly) - { - return "MSIL"; - } - else - { - // We return "x86" here because that seems to best match the Fusion-based - // GetAssemblyIdentityFromFile() method of acquiring the assembly identity. - return "x86"; - } - } - - private static string ReadString(MetadataReader reader, StringHandle handle) - { - return handle.IsNil ? null : reader.GetString(handle); - } - - private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle) - { - if (handle.IsNil) - { - return null; - } - - var bytes = reader.GetBlobBytes(handle); - if (bytes.Length == 0) - { - return null; - } - - var result = new StringBuilder(); - - // If we have the full public key, calculate the public key token from the - // last 8 bytes (in reverse order) of the public key's SHA1 hash. - if (bytes.Length > 8) - { - using (var sha1 = SHA1.Create()) - { - var hash = sha1.ComputeHash(bytes); - - for (var i = 1; i <= 8; ++i) - { - result.Append(hash[hash.Length - i].ToString("X2")); - } - } - } - else - { - foreach (var b in bytes) - { - result.Append(b.ToString("X2")); - } - } - - return result.ToString(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs deleted file mode 100644 index cfa84629..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. - /// - internal class AssignMediaCommand - { - private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB - - public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable fileFacades, bool compressed) - { - this.CabinetNameTemplate = "Cab{0}.cab"; - this.Section = section; - this.Messaging = messaging; - this.FileFacades = fileFacades; - this.FilesCompressed = compressed; - } - - private IntermediateSection Section { get; } - - private IMessaging Messaging { get; } - - private IEnumerable FileFacades { get; } - - private bool FilesCompressed { get; } - - private string CabinetNameTemplate { get; set; } - - /// - /// Gets cabinets with their file rows. - /// - public Dictionary> FileFacadesByCabinetMedia { 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() - { - var mediaSymbols = this.Section.Symbols.OfType().ToList(); - var mediaTemplateSymbols = this.Section.Symbols.OfType().ToList(); - - // If both symbols are authored, it is an error. - if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1) - { - throw new WixException(ErrorMessages.MediaTableCollision(null)); - } - - // If neither symbol is authored, default to a media template. - if (SectionType.Product == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0) - { - var mediaTemplate = new WixMediaTemplateSymbol() - { - CabinetTemplate = "cab{0}.cab", - }; - - this.Section.AddSymbol(mediaTemplate); - mediaTemplateSymbols.Add(mediaTemplate); - } - - // When building merge module, all the files go to "#MergeModule.CABinet". - if (SectionType.Module == this.Section.Type) - { - var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol - { - Cabinet = "#MergeModule.CABinet", - }); - - this.FileFacadesByCabinetMedia = new Dictionary> - { - { mergeModuleMediaSymbol, this.FileFacades } - }; - - this.UncompressedFileFacades = Array.Empty(); - } - else - { - var filesByCabinetMedia = new Dictionary>(); - var uncompressedFiles = new List(); - - if (mediaTemplateSymbols.Count > 0) - { - this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles); - } - else - { - this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles); - } - - this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value); - - this.UncompressedFileFacades = uncompressedFiles; - } - } - - /// - /// Assign files to cabinets based on MediaTemplate authoring. - /// - private void AutoAssignFiles(List mediaTemplateTable, List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) - { - const int MaxCabIndex = 999; - - ulong currentPreCabSize = 0; - ulong maxPreCabSizeInBytes; - var maxPreCabSizeInMB = 0; - var currentCabIndex = 0; - - MediaSymbol currentMediaRow = null; - - // Remove all previous media symbols since they will be replaced with - // media template. - foreach (var mediaSymbol in mediaSymbols) - { - this.Section.RemoveSymbol(mediaSymbol); - } - - // Auto assign files to cabinets based on maximum uncompressed media size - var mediaTemplateRow = mediaTemplateTable.Single(); - - if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) - { - this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; - } - - var 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 ?? DefaultMaximumUncompressedMediaSize; - } - - maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); - } - - var mediaSymbolsByDiskId = new Dictionary(); - - foreach (var 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 (SectionType.Product == this.Section.Type && (facade.Uncompressed || !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. - } - else - { - // Update current cab size. - currentPreCabSize += (ulong)facade.FileSize; - - // Overflow due to current file - if (currentPreCabSize > maxPreCabSizeInBytes) - { - currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); - mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - - // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize - currentPreCabSize = (ulong)facade.FileSize; - } - else // file fits in the current cab. - { - if (currentMediaRow == null) - { - // Create new cab and MediaRow - currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); - mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - } - } - } - - // Associate current file with current cab. - var cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.DiskId = currentCabIndex; - cabinetFiles.Add(facade); - } - - // If there are uncompressed files and no MediaRow, create a default one. - if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0) - { - var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1)) - { - DiskId = 1, - }); - - mediaSymbolsByDiskId.Add(1, defaultMediaRow); - } - } - - /// - /// Assign files to cabinets based on Media authoring. - /// - private void ManuallyAssignFiles(List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) - { - var mediaSymbolsByDiskId = new Dictionary(); - - if (mediaSymbols.Any()) - { - var cabinetMediaSymbols = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var mediaSymbol in mediaSymbols) - { - // If the Media row has a cabinet, make sure it is unique across all Media rows. - if (!String.IsNullOrEmpty(mediaSymbol.Cabinet)) - { - if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow)) - { - this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet)); - this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); - } - else - { - cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol); - } - - filesByCabinetMedia.Add(mediaSymbol, new List()); - } - - mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol); - } - } - - foreach (var facade in this.FileFacades) - { - if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol)) - { - this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); - continue; - } - - // When building a product, if the current file is to be uncompressed or if - // the package set not to be compressed, don't cab it. - var compressed = facade.Compressed; - var uncompressed = facade.Uncompressed; - if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed))) - { - uncompressedFiles.Add(facade); - } - else // file is marked compressed. - { - if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles)) - { - cabinetFiles.Add(facade); - } - else - { - this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId)); - } - } - } - } - - /// - /// Adds a symbol to the section with cab name template filled in. - /// - /// - /// - /// - private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex) - { - return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex)) - { - DiskId = cabIndex, - Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex), - CompressionLevel = mediaTemplateSymbol.CompressionLevel, - }); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs deleted file mode 100644 index 76bcd532..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs +++ /dev/null @@ -1,1305 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Text.RegularExpressions; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Services; - - /// - /// Include transforms in a patch. - /// - internal class AttachPatchTransformsCommand - { - private static readonly string[] PatchUninstallBreakingTables = new[] - { - "AppId", - "BindImage", - "Class", - "Complus", - "CreateFolder", - "DuplicateFile", - "Environment", - "Extension", - "Font", - "IniFile", - "IsolatedComponent", - "LockPermissions", - "MIME", - "MoveFile", - "MsiLockPermissionsEx", - "MsiServiceConfig", - "MsiServiceConfigFailureActions", - "ODBCAttribute", - "ODBCDataSource", - "ODBCDriver", - "ODBCSourceAttribute", - "ODBCTranslator", - "ProgId", - "PublishComponent", - "RemoveIniFile", - "SelfReg", - "ServiceControl", - "ServiceInstall", - "TypeLib", - "Verb", - }; - - private readonly TableDefinitionCollection tableDefinitions; - - public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable transforms) - { - this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Intermediate = intermediate; - this.Transforms = transforms; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private Intermediate Intermediate { get; } - - private IEnumerable Transforms { get; } - - public IEnumerable SubStorages { get; private set; } - - public IEnumerable Execute() - { - var subStorages = new List(); - - if (this.Transforms == null || !this.Transforms.Any()) - { - this.Messaging.Write(ErrorMessages.PatchWithoutTransforms()); - return subStorages; - } - - var summaryInfo = this.ExtractPatchSummaryInfo(); - - var section = this.Intermediate.Sections.First(); - - var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); - - // Get the patch id from the WixPatchId symbol. - var patchSymbol = symbols.OfType().FirstOrDefault(); - - if (String.IsNullOrEmpty(patchSymbol.Id?.Id)) - { - this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); - return subStorages; - } - - if (String.IsNullOrEmpty(patchSymbol.ClientPatchId)) - { - this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); - return subStorages; - } - - // enumerate patch.Media to map diskId to Media row - var patchMediaByDiskId = symbols.OfType().ToDictionary(t => t.DiskId); - - if (patchMediaByDiskId.Count == 0) - { - this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp()); - return subStorages; - } - - // populate MSP summary information - var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol); - - // enumerate transforms - var productCodes = new SortedSet(); - var transformNames = new List(); - var validTransform = new List>(); - - var baselineSymbolsById = symbols.OfType().ToDictionary(t => t.Id.Id); - - foreach (var mainTransform in this.Transforms) - { - var baselineSymbol = baselineSymbolsById[mainTransform.Baseline]; - - var patchRefSymbols = symbols.OfType().ToList(); - if (patchRefSymbols.Count > 0) - { - if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols)) - { - // transform has none of the content authored into this patch - continue; - } - } - - // Validate the transform doesn't break any patch specific rules. - this.Validate(mainTransform); - - // ensure consistent File.Sequence within each Media - var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; - - // Ensure that files are sequenced after the last file in any transform. - var transformMediaTable = mainTransform.Transform.Tables["Media"]; - if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) - { - foreach (MediaRow transformMediaRow in transformMediaTable.Rows) - { - if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < transformMediaRow.LastSequence) - { - // The Binder will pre-increment the sequence. - mediaSymbol.LastSequence = transformMediaRow.LastSequence; - } - } - } - - // Use the Media/@DiskId if greater than the last sequence for backward compatibility. - if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < mediaSymbol.DiskId) - { - mediaSymbol.LastSequence = mediaSymbol.DiskId; - } - - // Ignore media table in the transform. - mainTransform.Transform.Tables.Remove("Media"); - mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); - - var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); - - productCode = productCode.ToUpperInvariant(); - productCodes.Add(productCode); - validTransform.Add(Tuple.Create(productCode, mainTransform.Transform)); - - // attach these transforms to the patch object - // TODO: is this an acceptable way to auto-generate transform stream names? - var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture); - subStorages.Add(new SubStorage(transformName, mainTransform.Transform)); - subStorages.Add(new SubStorage("#" + transformName, pairedTransform)); - - transformNames.Add(":" + transformName); - transformNames.Add(":#" + transformName); - } - - if (validTransform.Count == 0) - { - this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms()); - return subStorages; - } - - // Validate that a patch authored as removable is actually removable - if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1") - { - var uninstallable = true; - - foreach (var entry in validTransform) - { - uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2); - } - - if (!uninstallable) - { - this.Messaging.Write(ErrorMessages.PatchNotRemovable()); - return subStorages; - } - } - - // Finish filling tables with transform-dependent data. - productCodes = FinalizePatchProductCodes(symbols, productCodes); - - // Semicolon delimited list of the product codes that can accept the patch. - summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) - { - PropertyId = SummaryInformationType.PatchProductCodes, - Value = String.Join(";", productCodes) - }); - - // Semicolon delimited list of transform substorage names in the order they are applied. - summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) - { - PropertyId = SummaryInformationType.TransformNames, - Value = String.Join(";", transformNames) - }); - - // Put the summary information that was extracted back in now that it is updated. - foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId)) - { - section.AddSymbol(readSummaryInfo); - } - - this.SubStorages = subStorages; - - return subStorages; - } - - private Dictionary ExtractPatchSummaryInfo() - { - var result = new Dictionary(); - - foreach (var section in this.Intermediate.Sections) - { - // Remove all summary information from the symbols and remember those that - // are not calculated or reserved. - foreach (var patchSummaryInfo in section.Symbols.OfType().ToList()) - { - section.RemoveSymbol(patchSummaryInfo); - - if (patchSummaryInfo.PropertyId != SummaryInformationType.PatchProductCodes && - patchSummaryInfo.PropertyId != SummaryInformationType.PatchCode && - patchSummaryInfo.PropertyId != SummaryInformationType.PatchInstallerRequirement && - patchSummaryInfo.PropertyId != SummaryInformationType.Reserved11 && - patchSummaryInfo.PropertyId != SummaryInformationType.Reserved14 && - patchSummaryInfo.PropertyId != SummaryInformationType.Reserved16) - { - result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo); - } - } - } - - return result; - } - - private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List symbols, WixPatchSymbol patchSymbol) - { - // PID_CODEPAGE - if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage)) - { - // Set the code page by default to the same code page for the - // string pool in the database. - AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers); - } - - // GUID patch code for the patch. - AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers); - - // Indicates the minimum Windows Installer version that is required to install the patch. - AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers); - - if (!summaryInfo.ContainsKey(SummaryInformationType.Security)) - { - AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced; - } - - // Use authored comments or default to display name. - MsiPatchMetadataSymbol commentsSymbol = null; - - var metadataSymbols = symbols.OfType().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property); - - if (!summaryInfo.ContainsKey(SummaryInformationType.Title) && - metadataSymbols.TryGetValue("DisplayName", out var displayName)) - { - AddSummaryInformation(SummaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers); - - // Default comments to use display name as-is. - commentsSymbol = displayName; - } - - // TODO: This code below seems unnecessary given the codepage is set at the top of this method. - //if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage) && - // metadataValues.TryGetValue("CodePage", out var codepage)) - //{ - // AddSummaryInformation(SummaryInformationType.Codepage, codepage); - //} - - if (!summaryInfo.ContainsKey(SummaryInformationType.PatchPackageName) && - metadataSymbols.TryGetValue("Description", out var description)) - { - AddSummaryInformation(SummaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers); - } - - if (!summaryInfo.ContainsKey(SummaryInformationType.Author) && - metadataSymbols.TryGetValue("ManufacturerName", out var manufacturer)) - { - AddSummaryInformation(SummaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers); - } - - // Special metadata marshalled through the build. - //var wixMetadataValues = symbols.OfType().ToDictionary(t => t.Id.Id, t => t.Value); - - //if (wixMetadataValues.TryGetValue("Comments", out var wixComments)) - if (metadataSymbols.TryGetValue("Comments", out var wixComments)) - { - commentsSymbol = wixComments; - } - - // Write the package comments to summary info. - if (!summaryInfo.ContainsKey(SummaryInformationType.Comments) && - commentsSymbol != null) - { - AddSummaryInformation(SummaryInformationType.Comments, commentsSymbol.Value, commentsSymbol.SourceLineNumbers); - } - - return metadataSymbols; - - void AddSummaryInformation(SummaryInformationType type, string value, SourceLineNumber sourceLineNumber) - { - summaryInfo.Add(type, new SummaryInformationSymbol(sourceLineNumber) - { - PropertyId = type, - Value = value - }); - } - } - - /// - /// Ensure transform is uninstallable. - /// - /// Product code in transform. - /// Transform generated by torch. - /// True if the transform is uninstallable - private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform) - { - var success = true; - - foreach (var tableName in PatchUninstallBreakingTables) - { - if (transform.TryGetTable(tableName, out var table)) - { - foreach (var row in table.Rows) - { - if (row.Operation == RowOperation.Add) - { - success = false; - - var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; - - this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); - } - } - } - } - - return success; - } - - /// - /// Reduce the transform according to the patch references. - /// - /// transform generated by torch. - /// Table contains patch family filter. - /// true if the transform is not empty - private bool ReduceTransform(WindowsInstallerData transform, IEnumerable patchRefSymbols) - { - // identify sections to keep - var oldSections = new Dictionary(); - var newSections = new Dictionary(); - var tableKeyRows = new Dictionary>(); - var sequenceList = new List(); - var componentFeatureAddsIndex = new Dictionary>(); - var customActionTable = new Dictionary(); - var directoryTableAdds = new Dictionary(); - var featureTableAdds = new Dictionary(); - var keptComponents = new Dictionary(); - var keptDirectories = new Dictionary(); - var keptFeatures = new Dictionary(); - var keptLockPermissions = new HashSet(); - var keptMsiLockPermissionExs = new HashSet(); - - var componentCreateFolderIndex = new Dictionary>(); - var directoryLockPermissionsIndex = new Dictionary>(); - var directoryMsiLockPermissionsExIndex = new Dictionary>(); - - foreach (var patchRefSymbol in patchRefSymbols) - { - var tableName = patchRefSymbol.Table; - var key = patchRefSymbol.PrimaryKeys; - - // Short circuit filtering if all changes should be included. - if ("*" == tableName && "*" == key) - { - RemoveProductCodeFromTransform(transform); - return true; - } - - if (!transform.Tables.TryGetTable(tableName, out var table)) - { - // Table not found. - continue; - } - - // Index the table. - if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) - { - keyRows = new Dictionary(); - tableKeyRows.Add(tableName, keyRows); - - foreach (var newRow in table.Rows) - { - var primaryKey = newRow.GetPrimaryKey(); - keyRows.Add(primaryKey, newRow); - } - } - - if (!keyRows.TryGetValue(key, out var row)) - { - // Row not found. - continue; - } - - // Differ.sectionDelimiter - var sections = row.SectionId.Split('/'); - oldSections[sections[0]] = row; - newSections[sections[1]] = row; - } - - // throw away sections not referenced - var keptRows = 0; - Table directoryTable = null; - Table featureTable = null; - Table lockPermissionsTable = null; - Table msiLockPermissionsTable = null; - - foreach (var 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 (var i = 0; i < table.Rows.Count; i++) - { - var row = table.Rows[i]; - - if (table.Name == "CreateFolder") - { - var createFolderComponentId = row.FieldAsString(1); - - if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList)) - { - directoryList = new List(); - componentCreateFolderIndex.Add(createFolderComponentId, directoryList); - } - - directoryList.Add(row.FieldAsString(0)); - } - - if (table.Name == "CustomAction") - { - customActionTable.Add(row.FieldAsString(0), row); - } - - if (table.Name == "Directory") - { - directoryTable = table; - if (RowOperation.Add == row.Operation) - { - directoryTableAdds.Add(row.FieldAsString(0), row); - } - } - - if (table.Name == "Feature") - { - featureTable = table; - if (RowOperation.Add == row.Operation) - { - featureTableAdds.Add(row.FieldAsString(0), row); - } - } - - if (table.Name == "FeatureComponents") - { - if (RowOperation.Add == row.Operation) - { - var featureId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); - - if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList)) - { - featureList = new List(); - componentFeatureAddsIndex.Add(componentId, featureList); - } - - featureList.Add(featureId); - } - } - - if (table.Name == "LockPermissions") - { - lockPermissionsTable = table; - if ("CreateFolder" == row.FieldAsString(1)) - { - var directoryId = row.FieldAsString(0); - - if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList)) - { - rowList = new List(); - directoryLockPermissionsIndex.Add(directoryId, rowList); - } - - rowList.Add(row); - } - } - - if (table.Name == "MsiLockPermissionsEx") - { - msiLockPermissionsTable = table; - if ("CreateFolder" == row.FieldAsString(1)) - { - var directoryId = row.FieldAsString(0); - - if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList)) - { - rowList = new List(); - directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); - } - - rowList.Add(row); - } - } - - if (null == row.SectionId) - { - table.Rows.RemoveAt(i); - i--; - } - else - { - var 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(row.FieldAsString(0), row); - } - - if ("Directory" == table.Name) - { - keptDirectories.Add(row.FieldAsString(0), row); - } - - if ("Feature" == table.Name) - { - keptFeatures.Add(row.FieldAsString(0), row); - } - - keptRows++; - } - else - { - table.Rows.RemoveAt(i); - i--; - } - } - } - } - - keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); - - if (null != directoryTable) - { - foreach (var componentRow in keptComponents.Values) - { - var componentId = componentRow.FieldAsString(0); - - if (RowOperation.Add == componentRow.Operation) - { - // Make sure each added component has its required directory and feature heirarchy. - var directoryId = componentRow.FieldAsString(2); - while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow)) - { - if (!keptDirectories.ContainsKey(directoryId)) - { - directoryTable.Rows.Add(directoryRow); - keptDirectories.Add(directoryId, directoryRow); - keptRows++; - } - - directoryId = directoryRow.FieldAsString(1); - } - - if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds)) - { - foreach (var featureId in componentFeatureIds) - { - var currentFeatureId = featureId; - while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow)) - { - if (!keptFeatures.ContainsKey(currentFeatureId)) - { - featureTable.Rows.Add(featureRow); - keptFeatures.Add(currentFeatureId, featureRow); - keptRows++; - } - - currentFeatureId = featureRow.FieldAsString(1); - } - } - } - } - - // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. - foreach (var keptComponentId in keptComponents.Keys) - { - if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList)) - { - foreach (var directoryId in directoryList) - { - if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList)) - { - foreach (var lockPermissionsRow in lockPermissionsRowList) - { - var key = lockPermissionsRow.GetPrimaryKey('/'); - if (keptLockPermissions.Add(key)) - { - lockPermissionsTable.Rows.Add(lockPermissionsRow); - keptRows++; - } - } - } - - if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList)) - { - foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList) - { - var key = msiLockPermissionsExRow.GetPrimaryKey('/'); - if (keptMsiLockPermissionExs.Add(key)) - { - msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); - keptRows++; - } - } - } - } - } - } - } - } - - keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); - - // Delete tables that are empty. - var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList(); - - foreach (var tableName in tablesToDelete) - { - transform.Tables.Remove(tableName); - } - - return keptRows > 0; - } - - private void Validate(PatchTransform patchTransform) - { - var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? - var transform = patchTransform.Transform; - - // Changing the ProdocutCode in a patch transform is not recommended. - if (transform.TryGetTable("Property", out var propertyTable)) - { - foreach (var row in propertyTable.Rows) - { - // Only interested in modified rows; fast check. - if (RowOperation.Modify == row.Operation && - "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended()); - } - } - } - - // If there is nothing in the component table we can return early because the remaining checks are component based. - if (!transform.TryGetTable("Component", out var componentTable)) - { - return; - } - - // Index Feature table row operations - var featureOps = new Dictionary(); - if (transform.TryGetTable("Feature", out var featureTable)) - { - foreach (var row in featureTable.Rows) - { - featureOps[row.FieldAsString(0)] = row.Operation; - } - } - - // Index Component table and check for keypath modifications - var componentKeyPath = new Dictionary(); - var deletedComponent = new Dictionary(); - foreach (var row in componentTable.Rows) - { - var id = row.FieldAsString(0); - var keypath = row.FieldAsString(5) ?? String.Empty; - - componentKeyPath.Add(id, keypath); - - if (RowOperation.Delete == row.Operation) - { - deletedComponent.Add(id, row); - } - else if (RowOperation.Modify == row.Operation) - { - if (row.Fields[1].Modified) - { - // Changing the guid of a component is equal to deleting the old one and adding a new one. - deletedComponent.Add(id, row); - } - - // If the keypath is modified its an error - if (row.Fields[5].Modified) - { - this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath)); - } - } - } - - // Verify changes in the file table - if (transform.TryGetTable("File", out var fileTable)) - { - var componentWithChangedKeyPath = new Dictionary(); - foreach (FileRow row in fileTable.Rows) - { - if (RowOperation.None == row.Operation) - { - continue; - } - - var fileId = row.File; - var componentId = row.Component; - - // If this file is the keypath of a component - if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) - { - if (row.Fields[2].Modified) - { - // You can't change the filename of a file that is the keypath of a component. - this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath)); - } - - if (!componentWithChangedKeyPath.ContainsKey(componentId)) - { - componentWithChangedKeyPath.Add(componentId, fileId); - } - } - - if (RowOperation.Delete == row.Operation) - { - // If the file is removed from a component that is not deleted. - if (!deletedComponent.ContainsKey(componentId)) - { - var foundRemoveFileEntry = false; - var filename = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), false, true); - - if (transform.TryGetTable("RemoveFile", out var removeFileTable)) - { - foreach (var removeFileRow in removeFileTable.Rows) - { - if (RowOperation.Delete == removeFileRow.Operation) - { - continue; - } - - if (componentId == removeFileRow.FieldAsString(1)) - { - // Check if there is a RemoveFile entry for this file - if (null != removeFileRow[2]) - { - var removeFileName = this.BackendHelper.GetMsiFileName(removeFileRow.FieldAsString(2), false, true); - - // Convert the MSI format for a wildcard string to Regex format. - removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); - - var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - if (regex.IsMatch(filename)) - { - foundRemoveFileEntry = true; - break; - } - } - } - } - } - - if (!foundRemoveFileEntry) - { - this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); - } - } - } - } - } - - var featureComponentsTable = transform.Tables["FeatureComponents"]; - - if (0 < deletedComponent.Count) - { - // Index FeatureComponents table. - var featureComponents = new Dictionary>(); - - if (null != featureComponentsTable) - { - foreach (var row in featureComponentsTable.Rows) - { - var componentId = row.FieldAsString(1); - - if (!featureComponents.TryGetValue(componentId, out var features)) - { - features = new List(); - featureComponents.Add(componentId, features); - } - - features.Add(row.FieldAsString(0)); - } - } - - // Check to make sure if a component was deleted, the feature was too. - foreach (var entry in deletedComponent) - { - if (featureComponents.TryGetValue(entry.Key, out var features)) - { - foreach (var featureId in features) - { - if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete) - { - // The feature was not deleted. - this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath)); - } - } - } - } - } - - // Warn if new components are added to existing features - if (null != featureComponentsTable) - { - foreach (var row in featureComponentsTable.Rows) - { - if (RowOperation.Add == row.Operation) - { - // Check if the feature is in the Feature table - var feature_ = row.FieldAsString(0); - var component_ = row.FieldAsString(1); - - // Features may not be present if not referenced - if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) - { - this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath)); - } - } - } - } - } - - /// - /// Remove the ProductCode property from the transform. - /// - /// The transform. - /// - /// Changing the ProductCode is not supported in a patch. - /// - private static void RemoveProductCodeFromTransform(WindowsInstallerData transform) - { - if (transform.Tables.TryGetTable("Property", out var propertyTable)) - { - for (var i = 0; i < propertyTable.Rows.Count; ++i) - { - var propertyRow = propertyTable.Rows[i]; - var 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 - /// Dictionary contains section id should be kept in the baseline wixout. - /// Dictionary contains section id should be kept in the upgrade wixout. - /// true if section in patch family - private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary oldSections, Dictionary newSections) - { - var result = false; - - if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection))) - { - result = true; - } - else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(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(List
sequenceList, Dictionary oldSections, Dictionary newSections, Dictionary customAction) - { - var keptRows = 0; - - foreach (var currentTable in sequenceList) - { - for (var i = 0; i < currentTable.Rows.Count; i++) - { - var row = currentTable.Rows[i]; - var actionName = row.Fields[0].Data.ToString(); - var sections = row.SectionId.Split('/'); - var 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) - { - var sequenceChanged = row.Fields[2].Modified; - var 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. - /// - private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) - { - productCode = null; - - var pairedTransform = new WindowsInstallerData(null) - { - Type = OutputType.Transform, - Codepage = mainTransform.Codepage - }; - - // lookup productVersion property to correct summaryInformation - var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1); - - var mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; - var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0)); - - var baselineValidationFlags = ((int)baselineSymbol.ValidationFlags).ToString(CultureInfo.InvariantCulture); - - if (!mainSummaryRows.ContainsKey((int)SummaryInformationType.TransformValidationFlags)) - { - var mainSummaryRow = mainSummaryTable.CreateRow(baselineSymbol.SourceLineNumbers); - mainSummaryRow[0] = (int)SummaryInformationType.TransformValidationFlags; - mainSummaryRow[1] = baselineValidationFlags; - } - - // copy summary information from core transform - var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); - - foreach (var mainSummaryRow in mainSummaryTable.Rows) - { - var type = (SummaryInformationType)mainSummaryRow.FieldAsInteger(0); - var value = mainSummaryRow.FieldAsString(1); - switch (type) - { - case SummaryInformationType.TransformProductCodes: - var propertyData = value.Split(';'); - var oldProductVersion = propertyData[0].Substring(38); - var 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 SummaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML. - value = baselineValidationFlags; - mainSummaryRow[1] = value; - break; - } - - var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers); - pairedSummaryRow[0] = mainSummaryRow[0]; - pairedSummaryRow[1] = value; - } - - if (productCode == null) - { - this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo()); - return null; - } - - // Copy File table - if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) - { - var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); - - foreach (FileRow mainFileRow in mainFileTable.Rows) - { - // 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; - } - - var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers); - pairedFileRow.Operation = RowOperation.Modify; - mainFileRow.CopyTo(pairedFileRow); - - // Override authored media for patch bind. - mainFileRow.DiskId = mediaSymbol.DiskId; - - // Suppress any change to File.Sequence to avoid bloat. - mainFileRow.Fields[7].Modified = false; - - // Force File row to appear in the transform. - switch (mainFileRow.Operation) - { - case RowOperation.Modify: - case RowOperation.Add: - pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = mainFileRow.Operation; - break; - default: - pairedFileRow.Fields[6].Modified = false; - break; - } - } - } - - // Add Media row to pairedTransform - var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); - var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaSymbol.SourceLineNumbers); - pairedMediaRow.Operation = RowOperation.Add; - pairedMediaRow.DiskId = mediaSymbol.DiskId; - pairedMediaRow.LastSequence = mediaSymbol.LastSequence ?? 0; - pairedMediaRow.DiskPrompt = mediaSymbol.DiskPrompt; - pairedMediaRow.Cabinet = mediaSymbol.Cabinet; - pairedMediaRow.VolumeLabel = mediaSymbol.VolumeLabel; - pairedMediaRow.Source = mediaSymbol.Source; - - // Add PatchPackage for this Media - var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); - pairedPackageTable.Operation = TableOperation.Add; - var pairedPackageRow = pairedPackageTable.CreateRow(mediaSymbol.SourceLineNumbers); - pairedPackageRow.Operation = RowOperation.Add; - pairedPackageRow[0] = patchIdSymbol.Id.Id; - pairedPackageRow[1] = mediaSymbol.DiskId; - - // Add the property to the patch transform's Property table. - var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); - pairedPropertyTable.Operation = TableOperation.Add; - - // Add property to both identify client patches and whether those patches are removable or not - patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalSymbol); - - var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalSymbol?.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".AllowRemoval"); - pairedPropertyRow[1] = allowRemovalSymbol?.Value ?? "0"; - - // Add this patch code GUID to the patch transform to identify - // which patches are installed, including in multi-patch - // installations. - pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".PatchCode"); - pairedPropertyRow[1] = patchIdSymbol.Id.Id; - - // Add PATCHNEWPACKAGECODE to apply to admin layouts. - pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; - pairedPropertyRow[1] = patchIdSymbol.Id.Id; - - // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts. - if (summaryInfo.TryGetValue(SummaryInformationType.Subject, out var subjectSymbol)) - { - pairedPropertyRow = pairedPropertyTable.CreateRow(subjectSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; - pairedPropertyRow[1] = subjectSymbol.Value; - } - - if (summaryInfo.TryGetValue(SummaryInformationType.Comments, out var commentsSymbol)) - { - pairedPropertyRow = pairedPropertyTable.CreateRow(commentsSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; - pairedPropertyRow[1] = commentsSymbol.Value; - } - - return pairedTransform; - } - - private static SortedSet FinalizePatchProductCodes(List symbols, SortedSet productCodes) - { - var patchTargetSymbols = symbols.OfType().ToList(); - - if (patchTargetSymbols.Any()) - { - var targets = new SortedSet(); - var replace = true; - foreach (var wixPatchTargetRow in patchTargetSymbols) - { - var target = wixPatchTargetRow.ProductCode.ToUpperInvariant(); - if (target == "*") - { - replace = false; - } - else - { - targets.Add(target); - } - } - - // Replace the target ProductCodes with the authored list. - if (replace) - { - productCodes = targets; - } - else - { - // Copy the authored target ProductCodes into the list. - foreach (var target in targets) - { - productCodes.Add(target); - } - } - } - - return productCodes; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs deleted file mode 100644 index 9f36cd78..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ /dev/null @@ -1,646 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Binds a databse. - /// - internal class BindDatabaseCommand - { - // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. - internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); - - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) - { - } - - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) - { - this.ServiceProvider = context.ServiceProvider; - - this.Messaging = context.ServiceProvider.GetService(); - - this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); - - this.PathResolver = this.ServiceProvider.GetService(); - - this.CabbingThreadCount = context.CabbingThreadCount; - this.CabCachePath = context.CabCachePath; - this.DefaultCompressionLevel = context.DefaultCompressionLevel; - this.DelayedFields = context.DelayedFields; - this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; - this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); - this.Intermediate = context.IntermediateRepresentation; - this.IntermediateFolder = context.IntermediateFolder; - this.OutputPath = context.OutputPath; - this.OutputPdbPath = context.PdbPath; - this.PdbType = context.PdbType; - this.ResolvedCodepage = context.ResolvedCodepage; - this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; - this.ResolvedLcid = context.ResolvedLcid; - this.SuppressLayout = context.SuppressLayout; - - this.SubStorages = subStorages; - - this.SuppressValidation = context.SuppressValidation; - this.Ices = context.Ices; - this.SuppressedIces = context.SuppressIces; - this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; - - this.BackendExtensions = backendExtension; - } - - public IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private int CabbingThreadCount { get; } - - private string CabCachePath { get; } - - private CompressionLevel? DefaultCompressionLevel { get; } - - public IEnumerable DelayedFields { get; } - - public IEnumerable ExpectedEmbeddedFiles { get; } - - public FileSystemManager FileSystemManager { get; } - - public bool DeltaBinaryPatch { get; set; } - - private IEnumerable BackendExtensions { get; } - - private IEnumerable SubStorages { get; } - - private Intermediate Intermediate { get; } - - private string OutputPath { get; } - - public PdbType PdbType { get; set; } - - private string OutputPdbPath { get; } - - private int? ResolvedCodepage { get; } - - private int? ResolvedSummaryInformationCodepage { get; } - - private int? ResolvedLcid { get; } - - private bool SuppressAddingValidationRows { get; } - - private bool SuppressLayout { get; } - - private string IntermediateFolder { get; } - - private bool SuppressValidation { get; } - - private IEnumerable Ices { get; } - - private IEnumerable SuppressedIces { get; } - - private IEnumerable CubeFiles { get; } - - public IBindResult Execute() - { - if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) - { - this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); - } - - var section = this.Intermediate.Sections.Single(); - - var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol(section) : null; - var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol(section) : null; - var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol(section) : null; - - var fileTransfers = new List(); - var trackedFiles = new List(); - - var containsMergeModules = false; - - // Load standard tables, authored custom tables, and extension custom tables. - TableDefinitionCollection tableDefinitions; - { - var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); - command.Execute(); - - tableDefinitions = command.TableDefinitions; - } - - // Calculate codepage - var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); - - // Process properties and create the delayed variable cache if needed. - Dictionary variableCache = null; - string productLanguage = null; - { - var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); - command.Execute(); - - variableCache = command.DelayedVariablesCache; - productLanguage = command.ProductLanguage; - } - - // Process the summary information table after properties are processed. - bool compressed; - bool longNames; - int installerVersion; - Platform platform; - string modularizationSuffix; - { - var branding = this.ServiceProvider.GetService(); - - var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); - command.Execute(); - - compressed = command.Compressed; - longNames = command.LongNames; - installerVersion = command.InstallerVersion; - platform = command.Platform; - modularizationSuffix = command.ModularizationSuffix; - } - - // Sequence all the actions. - { - var command = new SequenceActionsCommand(this.Messaging, section); - command.Execute(); - } - - if (section.Type == SectionType.Product || section.Type == SectionType.Module) - { - var command = new AddRequiredStandardDirectories(section, platform); - command.Execute(); - } - - { - var command = new CreateSpecialPropertiesCommand(section); - command.Execute(); - } - -#if TODO_PATCHING - ////if (OutputType.Patch == this.Output.Type) - ////{ - //// foreach (SubStorage substorage in this.Output.SubStorages) - //// { - //// Output transform = substorage.Data; - - //// ResolveFieldsCommand command = new ResolveFieldsCommand(); - //// command.Tables = transform.Tables; - //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - //// command.FileManagerCore = this.FileManagerCore; - //// command.FileManagers = this.FileManagers; - //// command.SupportDelayedResolution = false; - //// command.TempFilesLocation = this.TempFilesLocation; - //// command.WixVariableResolver = this.WixVariableResolver; - //// command.Execute(); - - //// this.MergeUnrealTables(transform.Tables); - //// } - ////} -#endif - - if (this.Messaging.EncounteredError) - { - return null; - } - - this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); - this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); - - // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). - { - var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); - - trackedFiles.AddRange(extractedFiles); - } - - // This must occur after all variables and source paths have been resolved. - List fileFacades; - if (SectionType.Patch == section.Type) - { - var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); - command.Execute(); - - fileFacades = command.FileFacades; - } - else - { - var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); - command.Execute(); - - fileFacades = command.FileFacades; - } - - // Retrieve file information from merge modules. - if (SectionType.Product == section.Type) - { - var wixMergeSymbols = section.Symbols.OfType().ToList(); - - if (wixMergeSymbols.Any()) - { - containsMergeModules = true; - - var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); - command.Execute(); - - fileFacades.AddRange(command.MergeModulesFileFacades); - } - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // Process SoftwareTags in MSI packages. - if (SectionType.Product == section.Type) - { - var softwareTags = section.Symbols.OfType().ToList(); - - if (softwareTags.Any()) - { - var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); - command.Execute(); - } - } - - // Gather information about files that do not come from merge modules. - { - var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); - command.Execute(); - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // Now that the variable cache is populated, resolve any delayed fields. - if (this.DelayedFields.Any()) - { - this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); - } - - // Update symbols that reference text files on disk. - { - var command = new UpdateFromTextFilesCommand(this.Messaging, section); - command.Execute(); - } - - // Add missing CreateFolder symbols to null-keypath components. - { - var command = new AddCreateFoldersCommand(section); - command.Execute(); - } - - // Process dependency references. - if (SectionType.Product == section.Type || SectionType.Module == section.Type) - { - var dependencyRefs = section.Symbols.OfType().ToList(); - - if (dependencyRefs.Any()) - { - var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); - command.Execute(); - } - } - - // If there are any backend extensions, give them the opportunity to process - // the section now that the fields have all be resolved. - // - if (this.BackendExtensions.Any()) - { - using (new IntermediateFieldContext("wix.bind.finalize")) - { - foreach (var extension in this.BackendExtensions) - { - extension.SymbolsFinalized(section); - } - - var reresolvedFiles = section.Symbols - .OfType() - .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize")) - .ToList(); - - if (reresolvedFiles.Any()) - { - var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); - - var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); - command.Execute(); - } - } - - if (this.Messaging.EncounteredError) - { - return null; - } - } - - // Set generated component guids and validate all guids. - { - var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); - command.Execute(); - } - - // Assign files to media and update file sequences. - Dictionary> filesByCabinetMedia; - IEnumerable uncompressedFiles; - { - var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades); - order.Execute(); - - fileFacades = order.FileFacades; - - var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); - assign.Execute(); - - filesByCabinetMedia = assign.FileFacadesByCabinetMedia; - uncompressedFiles = assign.UncompressedFileFacades; - - var update = new UpdateMediaSequencesCommand(section, fileFacades); - update.Execute(); - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. - WindowsInstallerData data; - { - var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); - data = command.Execute(); - } - - IEnumerable suppressedTableNames = null; - if (data.Type == OutputType.Module) - { - // Modularize identifiers. - var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType()); - modularize.Execute(); - - // Ensure all sequence tables in place because, mergemod.dll requires them. - var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); - suppressedTableNames = unsuppress.Execute(); - } - else if (data.Type == OutputType.Patch) - { - foreach (var storage in this.SubStorages) - { - data.SubStorages.Add(storage); - } - } - - // Stop processing if an error previously occurred. - if (this.Messaging.EncounteredError) - { - return null; - } - - // Ensure the intermediate folder is created since delta patches will be - // created there. - Directory.CreateDirectory(this.IntermediateFolder); - - if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) - { - var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); - command.Execute(); - } - - // create cabinet files and process uncompressed files - var layoutDirectory = Path.GetDirectoryName(this.OutputPath); - if (!this.SuppressLayout || OutputType.Module == data.Type) - { - this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); - - var mediaTemplate = section.Symbols.OfType().FirstOrDefault(); - - var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate); - command.CabbingThreadCount = this.CabbingThreadCount; - command.CabCachePath = this.CabCachePath; - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Data = data; - command.Messaging = this.Messaging; - command.BackendExtensions = this.BackendExtensions; - command.LayoutDirectory = layoutDirectory; - command.Compressed = compressed; - command.ModularizationSuffix = modularizationSuffix; - command.FileFacadesByCabinet = filesByCabinetMedia; - command.ResolveMedia = this.ResolveMedia; - command.TableDefinitions = tableDefinitions; - command.IntermediateFolder = this.IntermediateFolder; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // We can create instance transforms since Component Guids and Outputs are created. - if (data.Type == OutputType.Product) - { - var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); - command.Execute(); - } - else if (data.Type == OutputType.Patch) - { - // Copy output data back into the transforms. - var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades); - command.Execute(); - } - - // Generate database file. - { - this.Messaging.Write(VerboseMessages.GeneratingDatabase()); - - var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); - trackedFiles.Add(trackMsi); - - var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); - command.Execute(); - - trackedFiles.AddRange(command.GeneratedTemporaryFiles); - } - - // Stop processing if an error previously occurred. - if (this.Messaging.EncounteredError) - { - return null; - } - - // Merge modules. - if (containsMergeModules) - { - this.Messaging.Write(VerboseMessages.MergingModules()); - - var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Validate the output if there are CUBe files and we're not explicitly suppressing validation. - if (this.CubeFiles != null && !this.SuppressValidation) - { - var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); - command.Execute(); - - trackedFiles.AddRange(command.TrackedFiles); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Process uncompressed files. - if (!this.SuppressLayout && uncompressedFiles.Any()) - { - var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); - command.Compressed = compressed; - command.FileFacades = uncompressedFiles; - command.LayoutDirectory = layoutDirectory; - command.LongNamesInImage = longNames; - command.ResolveMedia = this.ResolveMedia; - command.DatabasePath = this.OutputPath; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - } - - // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). - trackedFiles.AddRange(fileFacades.Select(f => this.WindowsInstallerBackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); - - var result = this.ServiceProvider.GetService(); - result.FileTransfers = fileTransfers; - result.TrackedFiles = trackedFiles; - result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data); - - return result; - } - - private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) - { - var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; - - if (String.IsNullOrEmpty(codepage)) - { - codepage = this.ResolvedCodepage?.ToString() ?? "65001"; - - if (packageSymbol != null) - { - packageSymbol.Codepage = codepage; - } - else if (moduleSymbol != null) - { - moduleSymbol.Codepage = codepage; - } - else if (patchSymbol != null) - { - patchSymbol.Codepage = codepage; - } - } - - return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); - } - - private T GetSingleSymbol(IntermediateSection section) where T : IntermediateSymbol - { - var symbols = section.Symbols.OfType().ToList(); - - if (1 != symbols.Count) - { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); - } - - return symbols[0]; - } - - private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData data) - { - WixOutput wixout; - - if (String.IsNullOrEmpty(this.OutputPdbPath)) - { - wixout = WixOutput.Create(); - } - else - { - var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); - trackedFiles.Add(trackPdb); - - wixout = WixOutput.Create(trackPdb.Path); - } - - intermediate.Save(wixout); - - data.Save(wixout); - - wixout.Reopen(); - - return wixout; - } - - private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) - { - string layout = null; - - foreach (var extension in this.BackendExtensions) - { - layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); - if (!String.IsNullOrEmpty(layout)) - { - break; - } - } - - // If no binder file manager resolved the layout, do the default behavior. - if (String.IsNullOrEmpty(layout)) - { - if (String.IsNullOrEmpty(mediaLayoutDirectory)) - { - layout = layoutDirectory; - } - else if (Path.IsPathRooted(mediaLayoutDirectory)) - { - layout = mediaLayoutDirectory; - } - else - { - layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); - } - } - - return layout; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs deleted file mode 100644 index 41da2a13..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Binds the summary information table of a database. - /// - internal class BindSummaryInfoCommand - { - public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding) - { - this.Section = section; - this.SummaryInformationCodepage = summaryInformationCodepage; - this.ProductLanguage = productLanguage; - this.BackendHelper = backendHelper; - this.Branding = branding; - } - - private IntermediateSection Section { get; } - - private int? SummaryInformationCodepage { get; } - - private string ProductLanguage { get; } - - private IBackendHelper BackendHelper { get; } - - private IWixBranding Branding { get; } - - /// - /// 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; } - - public Platform Platform { get; private set; } - - /// - /// Modularization guid, or null if the output is not a module. - /// - public string ModularizationSuffix { get; private set; } - - public void Execute() - { - this.Compressed = false; - this.LongNames = false; - this.InstallerVersion = 0; - this.ModularizationSuffix = null; - - SummaryInformationSymbol summaryInformationCodepageSymbol = null; - SummaryInformationSymbol platformAndLanguageSymbol = null; - var foundCreateDateTime = false; - var foundLastSaveDataTime = false; - var foundCreatingApplication = false; - var foundPackageCode = false; - var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); - - foreach (var summaryInformationSymbol in this.Section.Symbols.OfType()) - { - switch (summaryInformationSymbol.PropertyId) - { - case SummaryInformationType.Codepage: // PID_CODEPAGE - summaryInformationCodepageSymbol = summaryInformationSymbol; - break; - - case SummaryInformationType.PlatformAndLanguage: - platformAndLanguageSymbol = summaryInformationSymbol; - break; - - case SummaryInformationType.PackageCode: // PID_REVNUMBER - foundPackageCode = true; - var packageCode = summaryInformationSymbol.Value; - - if (SectionType.Module == this.Section.Type) - { - this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_'); - } - break; - case SummaryInformationType.Created: - foundCreateDateTime = true; - break; - case SummaryInformationType.LastSaved: - foundLastSaveDataTime = true; - break; - case SummaryInformationType.WindowsInstallerVersion: - this.InstallerVersion = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); - break; - case SummaryInformationType.WordCount: - if (SectionType.Patch == this.Section.Type) - { - this.LongNames = true; - this.Compressed = true; - } - else - { - var attributes = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); - this.LongNames = (0 == (attributes & 1)); - this.Compressed = (2 == (attributes & 2)); - } - break; - case SummaryInformationType.CreatingApplication: // PID_APPNAME - foundCreatingApplication = true; - break; - } - } - - // Ensure the codepage is set properly. - if (summaryInformationCodepageSymbol == null) - { - summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.Codepage - }); - } - - var codepage = summaryInformationCodepageSymbol.Value; - - if (String.IsNullOrEmpty(codepage)) - { - codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252"; - } - - summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture); - - // Ensure the language is set properly and figure out what platform we are targeting. - if (platformAndLanguageSymbol != null) - { - this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage); - } - - // Set the revision number (package/patch code) if it should be automatically generated. - if (!foundPackageCode) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.PackageCode, - Value = this.BackendHelper.CreateGuid(), - }); - } - - // add a summary information row for the create time/date property if its not already set - if (!foundCreateDateTime) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.Created, - Value = now, - }); - } - - // add a summary information row for the last save time/date property if its not already set - if (!foundLastSaveDataTime) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.LastSaved, - Value = now, - }); - } - - // add a summary information row for the creating application property if its not already set - if (!foundCreatingApplication) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.CreatingApplication, - Value = this.Branding.GetCreatingApplication(), - }); - } - } - - private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language) - { - var value = symbol.Value; - var separatorIndex = value.IndexOf(';'); - var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; - - // If the language was provided and there was language value after the separator - // (or the separator was absent) then use the provided language. - if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length)) - { - symbol.Value = platformValue + ';' + language; - } - - switch (platformValue) - { - case "x64": - return Platform.X64; - - case "Arm64": - return Platform.ARM64; - - case "Intel": - default: - return Platform.X86; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs deleted file mode 100644 index 3379ec5d..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Globalization; - using System.IO; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class BindTransformCommand - { - public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.FileSystemManager = fileSystemManager; - this.IntermediateFolder = intermediateFolder; - this.Transform = transform; - this.OutputPath = outputPath; - this.TableDefinitions = tableDefinitions; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private FileSystemManager FileSystemManager { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private string IntermediateFolder { get; } - - private WindowsInstallerData Transform { get; } - - private string OutputPath { get; } - - public void Execute() - { - var transformFlags = 0; - - var targetOutput = new WindowsInstallerData(null); - var updatedOutput = new WindowsInstallerData(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; - - if (this.Transform.TryGetTable("Property", out var 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]; - } - } - } - } - - var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); - var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); - - // process special summary information values - foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) - { - var summaryId = row.FieldAsInteger(0); - var summaryData = row.FieldAsString(1); - - if ((int)SummaryInformation.Transform.CodePage == summaryId) - { - // convert from a web name if provided - var codePage = summaryData; - if (null == codePage) - { - codePage = "0"; - } - else - { - codePage = this.BackendHelper.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); - } - - var previousCodePage = row.Fields[1].PreviousData; - if (null == previousCodePage) - { - previousCodePage = "0"; - } - else - { - previousCodePage = this.BackendHelper.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); - } - - var targetCodePageRow = targetSummaryInfo.CreateRow(null); - targetCodePageRow[0] = 1; // PID_CODEPAGE - targetCodePageRow[1] = previousCodePage; - - var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); - updatedCodePageRow[0] = 1; // PID_CODEPAGE - updatedCodePageRow[1] = codePage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || - (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) - { - // the target language - var propertyData = summaryData.Split(';'); - var lang = 2 == propertyData.Length ? propertyData[1] : "0"; - - var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; - var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; - - var productLanguageRow = tempPropertyTable.CreateRow(null); - productLanguageRow[0] = "ProductLanguage"; - productLanguageRow[1] = lang; - - // set the platform;language on the MSI to be generated - var templateRow = tempSummaryInfo.CreateRow(null); - templateRow[0] = 7; // PID_TEMPLATE - templateRow[1] = summaryData; - } - else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) - { - var propertyData = summaryData.Split(';'); - - var targetProductCodeRow = targetPropertyTable.CreateRow(null); - targetProductCodeRow[0] = "ProductCode"; - targetProductCodeRow[1] = propertyData[0].Substring(0, 38); - - var targetProductVersionRow = targetPropertyTable.CreateRow(null); - targetProductVersionRow[0] = "ProductVersion"; - targetProductVersionRow[1] = propertyData[0].Substring(38); - - var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); - updatedProductCodeRow[0] = "ProductCode"; - updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); - - var 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)) - { - var 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)) - { - var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); - updatedUpgradeCodeRow[0] = "UpgradeCode"; - updatedUpgradeCodeRow[1] = updatedUpgradeCode; - } - } - else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) - { - transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) - { - // PID_LASTPRINTED should be null for transforms - row.Operation = RowOperation.None; - } - else - { - // add everything else as is - var targetRow = targetSummaryInfo.CreateRow(null); - targetRow[0] = row[0]; - targetRow[1] = row[1]; - - var 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))) - { - this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired()); - } - - string emptyFile = null; - - foreach (var 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 (var row in table.Rows) - { - switch (row.Operation) - { - case RowOperation.Add: - var updatedTable = updatedOutput.EnsureTable(table.Definition); - updatedTable.Rows.Add(row); - continue; - - case RowOperation.Delete: - var targetTable = targetOutput.EnsureTable(table.Definition); - targetTable.Rows.Add(row); - - // fill-in non-primary key values - foreach (var 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.IntermediateFolder, "empty"); - } - - field.Data = emptyFile; - } - else - { - field.Data = "0"; - } - } - } - continue; - } - - // Assure that the file table's sequence is populated - if ("File" == table.Name) - { - foreach (var fileRow in table.Rows) - { - if (null == fileRow[7]) - { - if (RowOperation.Add == fileRow.Operation) - { - this.Messaging.Write(ErrorMessages.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 - var modifiedRow = false; - var targetRow = table.Definition.CreateRow(null); - var updatedRow = row; - for (var i = 0; i < row.Fields.Length; i++) - { - var 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) - { - var data = updatedField.AsNullableInteger(); - targetRow[i] = (data == 1) ? 2 : 1; - } - else if (ColumnType.Object == updatedField.Column.Type) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.IntermediateFolder, "empty"); - } - - targetRow[i] = emptyFile; - } - else - { - var data = updatedField.AsString(); - targetRow[i] = (data == "0") ? "1" : "0"; - } - - modifiedRow = true; - } - else if (ColumnType.Object == updatedField.Column.Type) - { - var objectField = (ObjectField)updatedField; - - // create an empty file for comparing against - if (null == objectField.PreviousData) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.IntermediateFolder, "empty"); - } - - targetRow[i] = emptyFile; - modifiedRow = true; - } - else if (!this.FileSystemManager.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]))) - { - var targetTable = targetOutput.EnsureTable(table.Definition); - targetTable.Rows.Add(targetRow); - - var 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 (this.Messaging.EncounteredError) - { - return; - } - - var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); - var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); - var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); - - try - { - if (!String.IsNullOrEmpty(emptyFile)) - { - using (var fileStream = File.Create(emptyFile)) - { - } - } - - this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); - this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); - - // make sure the directory exists - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); - - // create the transform file - using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) - using (var 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 - { - this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); - } - } - } - finally - { - if (!String.IsNullOrEmpty(emptyFile)) - { - File.Delete(emptyFile); - } - } - } - - private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) - { - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); - command.Execute(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs deleted file mode 100644 index 13b079ad..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// 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 readonly Queue cabinetWorkItems; - private int threadCount; - - // Address of Binder's callback function for Cabinet Splitting - private readonly IntPtr newCabNamesCallBackAddress; - - /// - /// Instantiate a new CabinetBuilder. - /// - /// - /// number of threads to use - /// Address of Binder's callback function for Cabinet Splitting - public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress) - { - if (0 >= threadCount) - { - throw new ArgumentOutOfRangeException(nameof(threadCount)); - } - - this.cabinetWorkItems = new Queue(); - this.Messaging = messaging; - this.threadCount = threadCount; - - // Set Address of Binder's callback function for Cabinet Splitting - this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; - } - - private IMessaging Messaging { get; } - - public int MaximumCabinetSizeForLargeFileSplitting { get; set; } - - public int MaximumUncompressedMediaSize { get; set; } - - /// - /// 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 - var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count); - - if (0 < numberOfThreads) - { - var threads = new Thread[numberOfThreads]; - - for (var i = 0; i < threads.Length; i++) - { - threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); - threads[i].Start(); - } - - // wait for all threads to finish - foreach (var 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 = this.cabinetWorkItems.Dequeue(); - } - - // create a cabinet - this.CreateCabinet(cabinetWorkItem); - } - } - catch (WixException we) - { - this.Messaging.Write(we.Error); - } - catch (Exception e) - { - this.Messaging.Write(ErrorMessages.UnexpectedException(e)); - } - } - - /// - /// Creates a cabinet using the wixcab.dll interop layer. - /// - /// CabinetWorkItem containing information about the cabinet to create. - private void CreateCabinet(CabinetWorkItem cabinetWorkItem) - { - this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile)); - - var maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting - ulong maxPreCompressedSizeInBytes = 0; - - if (this.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)this.MaximumUncompressedMediaSize * 1024 * 1024; - - var facade = cabinetWorkItem.FileFacades.First(); - - // If the file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting - if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes) - { - maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting; - } - } - } - - // create the cabinet file - var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); - - var files = cabinetWorkItem.FileFacades - .OrderBy(f => f.Sequence) - .Select(facade => facade.Hash == null ? - new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) : - new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) - .ToList(); - - var cab = new Cabinet(cabinetPath); - cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); - - // TODO: Handle newCabNamesCallBackAddress from compression. - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs deleted file mode 100644 index 875b46c2..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CabinetResolver - { - public CabinetResolver(IServiceProvider serviceProvider, string cabCachePath, IEnumerable backendExtensions) - { - this.ServiceProvider = serviceProvider; - - this.CabCachePath = cabCachePath; - - this.BackendExtensions = backendExtensions; - } - - private IServiceProvider ServiceProvider { get; } - - private string CabCachePath { get; } - - private IEnumerable BackendExtensions { get; } - - public IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) - { - var filesWithPath = fileFacades.Select(this.CreateBindFileWithPath).ToList(); - - IResolvedCabinet 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 = this.ServiceProvider.GetService(); - resolved.BuildOption = CabinetBuildOption.BuildAndMove; - resolved.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)) - { - var 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 - var cabinetValid = true; - - var cabinet = new Cabinet(resolved.Path); - var fileList = cabinet.Enumerate(); - - if (filesWithPath.Count() != fileList.Count) - { - cabinetValid = false; - } - else - { - var i = 0; - foreach (var file in filesWithPath) - { - // First check that the file identifiers match because that is quick and easy. - var cabFileInfo = fileList[i]; - cabinetValid = (cabFileInfo.FileId == file.Id); - if (cabinetValid) - { - // Still valid so ensure the file sizes are the same. - var fileInfo = new FileInfo(file.Path); - cabinetValid = (cabFileInfo.Size == fileInfo.Length); - if (cabinetValid) - { - // Still valid so ensure the source time stamp hasn't changed. - cabinetValid = cabFileInfo.SameAsDateTime(fileInfo.LastWriteTime); - } - } - - if (!cabinetValid) - { - break; - } - - i++; - } - } - - resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; - } - } - - return resolved; - } - - private IBindFileWithPath CreateBindFileWithPath(IFileFacade facade) - { - var result = this.ServiceProvider.GetService(); - result.Id = facade.Id; - result.Path = facade.SourcePath; - - return result; - } - - private static bool CheckFileExists(string path) - { - try - { - return File.Exists(path); - } - catch (ArgumentException) - { - throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs deleted file mode 100644 index 1990ea78..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.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.Core.WindowsInstaller.Bind -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - /// - /// A cabinet builder work item. - /// - internal sealed class CabinetWorkItem - { - /// - /// 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. - /// Modularization suffix used when building a Merge Module. - /// - public CabinetWorkItem(IEnumerable fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix /*, BinderFileManager binderFileManager*/) - { - this.CabinetFile = cabinetFile; - this.CompressionLevel = compressionLevel; - this.ModularizationSuffix = modularizationSuffix; - this.FileFacades = fileFacades; - //this.BinderFileManager = binderFileManager; - this.MaxThreshold = maxThreshold; - } - - /// - /// Gets the cabinet file. - /// - /// The cabinet file. - public string CabinetFile { get; } - - /// - /// Gets the compression level of the cabinet. - /// - /// The compression level of the cabinet. - public CompressionLevel CompressionLevel { get; } - - /// - /// Gets the modularization suffix used when building a Merge Module. - /// - public string ModularizationSuffix { get; } - - /// - /// Gets the collection of files in this cabinet. - /// - /// The collection of files in this cabinet. - public IEnumerable FileFacades { get; } - - // - // Gets the binder file manager. - // - // The binder file manager. - //public BinderFileManager BinderFileManager { get; private set; } - - /// - /// Gets the max threshold. - /// - /// The maximum threshold for a folder in a cabinet. - public int MaxThreshold { get; } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs deleted file mode 100644 index 83a4949e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// 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 readonly List fileTransfers; - - private readonly List trackedFiles; - - private readonly FileSplitCabNamesCallback newCabNamesCallBack; - - private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence - - public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate) - { - this.fileTransfers = new List(); - - this.trackedFiles = new List(); - - this.newCabNamesCallBack = this.NewCabNamesCallBack; - - this.ServiceProvider = serviceProvider; - - this.BackendHelper = backendHelper; - - this.MediaTemplate = mediaTemplate; - } - - private IServiceProvider ServiceProvider { get; } - - private IBackendHelper BackendHelper { get; } - - private WixMediaTemplateSymbol MediaTemplate { get; } - - /// - /// Sets the number of threads to use for cabinet creation. - /// - public int CabbingThreadCount { private get; set; } - - public string CabCachePath { private get; set; } - - public IMessaging Messaging { private get; set; } - - public string IntermediateFolder { 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 WindowsInstallerData Data { private get; set; } - - public string LayoutDirectory { private get; set; } - - public bool Compressed { private get; set; } - - public string ModularizationSuffix { private get; set; } - - public Dictionary> FileFacadesByCabinet { private get; set; } - - public Func ResolveMedia { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public IEnumerable FileTransfers => this.fileTransfers; - - public IEnumerable TrackedFiles => this.trackedFiles; - - public void Execute() - { - this.lastCabinetAddedToMediaTable = new Dictionary(); - - // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. - if (this.CabbingThreadCount <= 0) - { - this.CabbingThreadCount = this.CalculateCabbingThreadCount(); - - this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); - } - - // Send Binder object to Facilitate NewCabNamesCallBack Callback - var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); - - // Supply Compile MediaTemplate Attributes to Cabinet Builder - this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); - cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; - cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; - - foreach (var entry in this.FileFacadesByCabinet) - { - var mediaSymbol = entry.Key; - var files = entry.Value; - var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; - var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); - - var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); - if (null != cabinetWorkItem) - { - cabinetBuilder.Enqueue(cabinetWorkItem); - } - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return; - } - - // create queued cabinets with multiple threads - cabinetBuilder.CreateQueuedCabinets(); - if (this.Messaging.EncounteredError) - { - return; - } - } - - private int CalculateCabbingThreadCount() - { - var cabbingThreadCount = Environment.ProcessorCount; - - if (cabbingThreadCount <= 0) - { - cabbingThreadCount = 1; // reset to 1 when the environment variable is invalid. - - this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), cabbingThreadCount.ToString())); - } - - return cabbingThreadCount; - } - - /// - /// Creates a work item to create a cabinet. - /// - /// Windows Installer data for the current database. - /// Directory to create cabinet in. - /// Media symbol containing information about the cabinet. - /// Desired compression level. - /// Collection of files in this cabinet. - /// created CabinetWorkItem object - private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades) - { - CabinetWorkItem cabinetWorkItem = null; - var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); - - // check for an empty cabinet - if (!fileFacades.Any()) - { - // Remove the leading '#' from the embedded cabinet name to make the warning easier to understand - var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); - - // If building a patch, remind them to run -p for torch. - if (OutputType.Patch == data.Type) - { - this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true)); - } - else - { - this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName)); - } - } - - var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); - - var 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) - { - // Default to the threshold for best smartcabbing (makes smallest cabinet). - cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold: 0, compressionLevel, this.ModularizationSuffix /*, this.FileManager*/); - } - else // reuse the cabinet from the cabinet cache. - { - this.Messaging.Write(VerboseMessages.ReusingCabCache(mediaSymbol.SourceLineNumbers, mediaSymbol.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) - { - this.Messaging.Write(WarningMessages.CannotUpdateCabCache(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, e.Message)); - } - } - - var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaSymbol.SourceLineNumbers); - this.trackedFiles.Add(trackResolvedCabinet); - - if (mediaSymbol.Cabinet.StartsWith("#", StringComparison.Ordinal)) - { - var streamsTable = data.EnsureTable(this.TableDefinitions["_Streams"]); - - var streamRow = streamsTable.CreateRow(mediaSymbol.SourceLineNumbers); - streamRow[0] = mediaSymbol.Cabinet.Substring(1); - streamRow[1] = resolvedCabinet.Path; - } - else - { - var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaSymbol.Cabinet), TrackedFileType.Final, mediaSymbol.SourceLineNumbers); - this.trackedFiles.Add(trackDestination); - - var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaSymbol.SourceLineNumbers); - this.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 newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) - { - throw new NotImplementedException(); -#if TODO_CAB_SPANNING - // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads - var mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); - try - { - if (!mutex.WaitOne(0, false)) // Check if you can get the lock - { - // Cound not get the Lock - this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel()); - mutex.WaitOne(); // Wait on other thread - } - - var firstCabinetName = firstCabName + ".cab"; - var transferAdded = false; // Used for Error Handling - - // Create File Transfer for new Cabinet using transfer of Base Cabinet - foreach (var transfer in this.FileTransfers) - { - if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) - { - var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); - var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); - - var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); - this.trackedFiles.Add(trackSource); - - var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers); - this.trackedFiles.Add(trackTarget); - - var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); - this.fileTransfers.Add(newTransfer); - - transferAdded = true; - break; - } - } - - // Check if File Transfer was added - if (!transferAdded) - { - throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); - } - - // Add the new Cabinets to media table using LastSequence of Base Cabinet - var mediaTable = this.Output.Tables["Media"]; - var wixFileTable = this.Output.Tables["WixFile"]; - var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain - var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain - var lastSplitCabinetFound = false; // Used for Error Handling - - var 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(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); - } - } - - // Check if the last Split Cabinet was found in the Media Table - if (!lastSplitCabinetFound) - { - throw new WixException(ErrorMessages.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(); - } -#endif - } - - - /// - /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides - /// - private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) - { - // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size - var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); - var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); - - // Supply Compile MediaTemplate Attributes to Cabinet Builder - if (this.MediaTemplate != null) - { - // Get the Value for Max Cab Size for File Splitting - var maxCabSizeForLargeFileInMB = 0; - try - { - // Override authored mcslfs value if environment variable is authored. - maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; - - var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; - maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); - } - - var maxPreCompressedSizeInMB = 0; - try - { - // Override authored mums value if environment variable is authored. - maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; - - var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; - maxUncompressedMediaSize = maxPreCompressedSizeInMB; - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); - } - } - else - { - maxCabSizeForLargeFileSplitting = 0; - maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs deleted file mode 100644 index 47d8399f..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - - /// - /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. - /// - internal class CreateDeltaPatchesCommand - { - public CreateDeltaPatchesCommand(List fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId) - { - this.FileFacades = fileFacades; - this.IntermediateFolder = intermediateFolder; - this.WixPatchId = wixPatchId; - } - - private IEnumerable FileFacades { get; } - - private WixPatchSymbol WixPatchId { get; } - - private string IntermediateFolder { get; } - - public void Execute() - { - var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; - var apiPatchingSymbolFlags = (PatchSymbolFlags)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); - -#if TODO_PATCHING_DELTA - 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.IntermediateFolder, String.Concat(deltaBase, ".dpf")); - string headerFile = Path.Combine(this.IntermediateFolder, 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)); - } - } - } -#endif - - throw new NotImplementedException(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs deleted file mode 100644 index ff03413c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Globalization; - using System.IO; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class CreateIdtFileCommand - { - public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns) - { - this.Messaging = messaging; - this.Table = table; - this.Codepage = codepage; - this.IntermediateFolder = intermediateFolder; - this.KeepAddedColumns = keepAddedColumns; - } - - private IMessaging Messaging { get; } - - private Table Table { get; } - - private int Codepage { get; set; } - - private string IntermediateFolder { get; } - - private bool KeepAddedColumns { get; } - - public string IdtPath { get; private set; } - - public void Execute() - { - // write out the table to an IDT file - var encoding = GetCodepageEncoding(this.Codepage); - - this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); - - using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) - { - this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); - } - } - - private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) - { - if (table.Definition.Unreal) - { - return; - } - - if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length) - { - throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable)); - } - - // Tack on the table header, and flush before we start writing bytes directly to the stream. - var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); - writer.Write(header); - writer.Flush(); - - using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) - { - // Create an encoding that replaces characters with question marks, and doesn't throw. We'll - // use this in case of errors - Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); - - foreach (Row row in table.Rows) - { - if (row.Redundant) - { - continue; - } - - string rowString = this.RowToIdtDefinition(row, keepAddedColumns); - byte[] rowBytes; - - try - { - // GetBytes will throw an exception if any character doesn't match our current encoding - rowBytes = writer.Encoding.GetBytes(rowString); - } - catch (EncoderFallbackException) - { - this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); - - rowBytes = convertEncoding.GetBytes(rowString); - } - - binary.Write(rowBytes, 0, rowBytes.Length); - } - } - } - - private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) - { - var first = true; - var columnString = new StringBuilder(); - var dataString = new StringBuilder(); - var tableString = new StringBuilder(); - - tableString.Append(definition.Name); - foreach (var column in definition.Columns) - { - // Conditionally keep columns added in a transform; otherwise, - // break because columns can only be added at the end. - if (column.Added && !keepAddedColumns) - { - break; - } - - if (column.Unreal) - { - continue; - } - - if (!first) - { - columnString.Append('\t'); - dataString.Append('\t'); - } - - columnString.Append(column.Name); - dataString.Append(ColumnIdtType(column)); - - if (column.PrimaryKey) - { - tableString.AppendFormat("\t{0}", column.Name); - } - - first = false; - } - columnString.Append("\r\n"); - columnString.Append(dataString); - columnString.Append("\r\n"); - columnString.Append(tableString); - columnString.Append("\r\n"); - - return columnString.ToString(); - } - - private string RowToIdtDefinition(Row row, bool keepAddedColumns) - { - var first = true; - var sb = new StringBuilder(); - - foreach (var field in row.Fields) - { - // Conditionally keep columns added in a transform; otherwise, - // break because columns can only be added at the end. - if (field.Column.Added && !keepAddedColumns) - { - break; - } - - if (field.Column.Unreal) - { - continue; - } - - if (first) - { - first = false; - } - else - { - sb.Append('\t'); - } - - sb.Append(this.FieldToIdtValue(field)); - } - sb.Append("\r\n"); - - return sb.ToString(); - } - - private string FieldToIdtValue(Field field) - { - var data = field.AsString(); - - if (String.IsNullOrEmpty(data)) - { - return data; - } - - // Special field value idt-specific escaping. - return data.Replace('\t', '\x10') - .Replace('\r', '\x11') - .Replace('\n', '\x19'); - } - - private static Encoding GetCodepageEncoding(int codepage) - { - Encoding encoding; - - // If UTF8 encoding, use the UTF8-specific constructor to avoid writing - // the byte order mark at the beginning of the file - if (codepage == Encoding.UTF8.CodePage) - { - encoding = new UTF8Encoding(false, true); - } - else - { - if (codepage == 0) - { - codepage = Encoding.ASCII.CodePage; - } - - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - } - - return encoding; - } - - /// - /// Gets the type of the column in IDT format. - /// - /// IDT format for column type. - private static string ColumnIdtType(ColumnDefinition column) - { - char typeCharacter; - switch (column.Type) - { - case ColumnType.Number: - typeCharacter = column.Nullable ? 'I' : 'i'; - break; - case ColumnType.Preserved: - case ColumnType.String: - typeCharacter = column.Nullable ? 'S' : 's'; - break; - case ColumnType.Localized: - typeCharacter = column.Nullable ? 'L' : 'l'; - break; - case ColumnType.Object: - typeCharacter = column.Nullable ? 'V' : 'v'; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); - } - - return String.Concat(typeCharacter, column.Length); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs deleted file mode 100644 index d0e25571..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Services; - - internal class CreateInstanceTransformsCommand - { - public CreateInstanceTransformsCommand(IntermediateSection section, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, IBackendHelper backendHelper) - { - this.Section = section; - this.Output = output; - this.TableDefinitions = tableDefinitions; - this.BackendHelper = backendHelper; - } - - private IntermediateSection Section { get; } - - private WindowsInstallerData Output { get; } - - public TableDefinitionCollection TableDefinitions { get; } - - private IBackendHelper BackendHelper { get; } - - public void Execute() - { - // Create and add substorages for instance transforms. - var wixInstanceTransformsSymbols = this.Section.Symbols.OfType(); - - if (wixInstanceTransformsSymbols.Any()) - { - string targetProductCode = null; - string targetUpgradeCode = null; - string targetProductVersion = null; - - var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"]; - var targetPropertyTable = this.Output.Tables["Property"]; - - // Get the data from target database - foreach (var 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, we'll get the Components rows from the real Component table. - var targetInstanceComponentTable = this.Section.Symbols.OfType(); - var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null); - - if (instanceComponentGuids.Any()) - { - var targetComponentTable = this.Output.Tables["Component"]; - foreach (ComponentRow componentRow in targetComponentTable.Rows) - { - var component = (string)componentRow[0]; - if (instanceComponentGuids.ContainsKey(component)) - { - instanceComponentGuids[component] = componentRow; - } - } - } - - // Generate the instance transforms - foreach (var instanceSymbol in wixInstanceTransformsSymbols) - { - var instanceId = instanceSymbol.Id.Id; - - var instanceTransform = new WindowsInstallerData(instanceSymbol.SourceLineNumbers); - instanceTransform.Type = OutputType.Transform; - instanceTransform.Codepage = this.Output.Codepage; - - var instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - string targetPlatformAndLanguage = null; - - foreach (var summaryInformationRow in targetSummaryInformationTable.Rows) - { - if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE - { - targetPlatformAndLanguage = (string)summaryInformationRow[1]; - } - - // Copy the row's data to the transform. - var copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(summaryInformationRow.SourceLineNumbers); - copyOfSummaryRow[0] = summaryInformationRow[0]; - copyOfSummaryRow[1] = summaryInformationRow[1]; - } - - // Modify the appropriate properties. - var propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); - - // Change the ProductCode property - var productCode = instanceSymbol.ProductCode; - if ("*" == productCode) - { - productCode = this.BackendHelper.CreateGuid(); - } - - var productCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - productCodeRow.Operation = RowOperation.Modify; - productCodeRow.Fields[1].Modified = true; - productCodeRow[0] = "ProductCode"; - productCodeRow[1] = productCode; - - // Change the instance property - var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - instanceIdRow.Operation = RowOperation.Modify; - instanceIdRow.Fields[1].Modified = true; - instanceIdRow[0] = instanceSymbol.PropertyId; - instanceIdRow[1] = instanceId; - - if (!String.IsNullOrEmpty(instanceSymbol.ProductName)) - { - // Change the ProductName property - var productNameRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - productNameRow.Operation = RowOperation.Modify; - productNameRow.Fields[1].Modified = true; - productNameRow[0] = "ProductName"; - productNameRow[1] = instanceSymbol.ProductName; - } - - if (!String.IsNullOrEmpty(instanceSymbol.UpgradeCode)) - { - // Change the UpgradeCode property - var upgradeCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - upgradeCodeRow.Operation = RowOperation.Modify; - upgradeCodeRow.Fields[1].Modified = true; - upgradeCodeRow[0] = "UpgradeCode"; - upgradeCodeRow[1] = instanceSymbol.UpgradeCode; - - // Change the Upgrade table - var targetUpgradeTable = this.Output.Tables["Upgrade"]; - if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) - { - var upgradeId = instanceSymbol.UpgradeCode; - var upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); - foreach (var 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]) - { - var upgradeRow = upgradeTable.CreateRow(row.SourceLineNumbers); - 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 - var upgradeRemoveRow = upgradeTable.CreateRow(row.SourceLineNumbers); - 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) - { - var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); - foreach (var targetComponentRow in instanceComponentGuids.Values) - { - var guid = targetComponentRow.Guid; - if (!String.IsNullOrEmpty(guid)) - { - var instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); - instanceComponentRow.Operation = RowOperation.Modify; - instanceComponentRow.Fields[1].Modified = true; - instanceComponentRow[0] = targetComponentRow[0]; - instanceComponentRow[1] = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)); - instanceComponentRow[2] = targetComponentRow[2]; - instanceComponentRow[3] = targetComponentRow[3]; - instanceComponentRow[4] = targetComponentRow[4]; - instanceComponentRow[5] = targetComponentRow[5]; - } - } - } - - // Update the summary information - var summaryRows = new Dictionary(instanceSummaryInformationTable.Rows.Count); - foreach (var row in instanceSummaryInformationTable.Rows) - { - summaryRows[(int)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.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = targetPlatformAndLanguage; - } - else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) - { - var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = "0"; - } - else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) - { - var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); - summaryRow[0] = (int)SummaryInformation.Transform.Security; - summaryRow[1] = "4"; - } - - this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs deleted file mode 100644 index 5c993f63..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class CreatePatchTransformsCommand - { - public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Intermediate = intermediate; - this.IntermediateFolder = intermediateFolder; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private Intermediate Intermediate { get; } - - private string IntermediateFolder { get; } - - public IEnumerable PatchTransforms { get; private set; } - - public IEnumerable Execute() - { - var patchTransforms = new List(); - - var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType(); - - foreach (var symbol in symbols) - { - WindowsInstallerData transform; - - if (symbol.TransformFile is null) - { - var baselineData = this.GetData(symbol.BaselineFile.Path); - var updateData = this.GetData(symbol.UpdateFile.Path); - - var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); - transform = command.Execute(); - } - else - { - var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. - - var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder); - transform = command.Execute(); - } - - patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); - } - - this.PatchTransforms = patchTransforms; - - return this.PatchTransforms; - } - - private WindowsInstallerData GetData(string path) - { - var ext = Path.GetExtension(path); - - if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) - { - using (var database = new Database(path, OpenDatabase.ReadOnly)) - { - var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. - - var isAdminImage = false; // TODO: need a better way to set this - - var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); - return command.Execute(); - } - } - else // assume .wixpdb (or .wixout) - { - var data = WindowsInstallerData.Load(path, true); - return data; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs deleted file mode 100644 index ba7c03a0..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class CreateSpecialPropertiesCommand - { - public CreateSpecialPropertiesCommand(IntermediateSection section) - { - this.Section = section; - } - - private IntermediateSection Section { get; } - - public void Execute() - { - // Create lists of the properties that contribute to the special lists of properties. - var adminProperties = new SortedSet(); - var secureProperties = new SortedSet(); - var hiddenProperties = new SortedSet(); - - foreach (var wixPropertyRow in this.Section.Symbols.OfType()) - { - if (wixPropertyRow.Admin) - { - adminProperties.Add(wixPropertyRow.PropertyRef); - } - - if (wixPropertyRow.Hidden) - { - hiddenProperties.Add(wixPropertyRow.PropertyRef); - } - - if (wixPropertyRow.Secure) - { - secureProperties.Add(wixPropertyRow.PropertyRef); - } - } - - // Hide properties for in-script custom actions that have HideTarget set. - var hideTargetCustomActions = this.Section.Symbols.OfType().Where( - ca => ca.Hidden - && (ca.ExecutionType == CustomActionExecutionType.Deferred - || ca.ExecutionType == CustomActionExecutionType.Commit - || ca.ExecutionType == CustomActionExecutionType.Rollback)) - .Select(ca => ca.Id.Id); - hiddenProperties.UnionWith(hideTargetCustomActions); - - // Ensure upgrade action properties are secure. - var actionProperties = this.Section.Symbols.OfType().Select(u => u.ActionProperty); - secureProperties.UnionWith(actionProperties); - - if (0 < adminProperties.Count) - { - this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "AdminProperties")) - { - Value = String.Join(";", adminProperties), - }); - } - - if (0 < secureProperties.Count) - { - this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "SecureCustomProperties")) - { - Value = String.Join(";", secureProperties), - }); - } - - if (0 < hiddenProperties.Count) - { - this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "MsiHiddenProperties")) - { - Value = String.Join(";", hiddenProperties) - }); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs deleted file mode 100644 index d34ca3fe..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ /dev/null @@ -1,1621 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class CreateWindowsInstallerDataFromIRCommand - { - private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - - public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) - { - this.Messaging = messaging; - this.Section = section; - this.TableDefinitions = tableDefinitions; - this.Codepage = codepage; - this.BackendExtensions = backendExtensions; - this.BackendHelper = backendHelper; - this.GeneratedShortNames = new Dictionary>(); - } - - private IEnumerable BackendExtensions { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - private IMessaging Messaging { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private int Codepage { get; } - - private IntermediateSection Section { get; } - - private Dictionary> GeneratedShortNames { get; } - - public WindowsInstallerData Data { get; private set; } - - public WindowsInstallerData Execute() - { - this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers) - { - Codepage = this.Codepage, - Type = SectionTypeToOutputType(this.Section.Type) - }; - - this.AddSectionToData(); - - return this.Data; - } - - private void AddSectionToData() - { - var cellsByTableAndRowId = new Dictionary>(); - - foreach (var symbol in this.Section.Symbols) - { - var unknownSymbol = false; - switch (symbol.Definition.Type) - { - case SymbolDefinitionType.AppSearch: - this.AddSymbolDefaultly(symbol); - this.Data.EnsureTable(this.TableDefinitions["Signature"]); - break; - - case SymbolDefinitionType.Assembly: - this.AddAssemblySymbol((AssemblySymbol)symbol); - break; - - case SymbolDefinitionType.BBControl: - this.AddBBControlSymbol((BBControlSymbol)symbol); - break; - - case SymbolDefinitionType.Class: - this.AddClassSymbol((ClassSymbol)symbol); - break; - - case SymbolDefinitionType.Control: - this.AddControlSymbol((ControlSymbol)symbol); - break; - - case SymbolDefinitionType.ControlEvent: - this.AddControlEventSymbol((ControlEventSymbol)symbol); - break; - - case SymbolDefinitionType.Component: - this.AddComponentSymbol((ComponentSymbol)symbol); - break; - - case SymbolDefinitionType.CustomAction: - this.AddCustomActionSymbol((CustomActionSymbol)symbol); - break; - - case SymbolDefinitionType.Dialog: - this.AddDialogSymbol((DialogSymbol)symbol); - break; - - case SymbolDefinitionType.Directory: - this.AddDirectorySymbol((DirectorySymbol)symbol); - break; - - case SymbolDefinitionType.DuplicateFile: - this.AddDuplicateFileSymbol((DuplicateFileSymbol)symbol); - break; - - case SymbolDefinitionType.Environment: - this.AddEnvironmentSymbol((EnvironmentSymbol)symbol); - break; - - case SymbolDefinitionType.Error: - this.AddErrorSymbol((ErrorSymbol)symbol); - break; - - case SymbolDefinitionType.Feature: - this.AddFeatureSymbol((FeatureSymbol)symbol); - break; - - case SymbolDefinitionType.File: - this.AddFileSymbol((FileSymbol)symbol); - break; - - case SymbolDefinitionType.IniFile: - this.AddIniFileSymbol((IniFileSymbol)symbol); - break; - - case SymbolDefinitionType.IniLocator: - this.AddIniLocatorSymbol((IniLocatorSymbol)symbol); - break; - - case SymbolDefinitionType.Media: - this.AddMediaSymbol((MediaSymbol)symbol); - break; - - case SymbolDefinitionType.ModuleConfiguration: - this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol); - this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration"); - break; - - case SymbolDefinitionType.ModuleSubstitution: - this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution"); - break; - - case SymbolDefinitionType.MsiEmbeddedUI: - this.AddMsiEmbeddedUISymbol((MsiEmbeddedUISymbol)symbol); - break; - - case SymbolDefinitionType.MsiServiceConfig: - this.AddMsiServiceConfigSymbol((MsiServiceConfigSymbol)symbol); - break; - - case SymbolDefinitionType.MsiServiceConfigFailureActions: - this.AddMsiServiceConfigFailureActionsSymbol((MsiServiceConfigFailureActionsSymbol)symbol); - break; - - case SymbolDefinitionType.MoveFile: - this.AddMoveFileSymbol((MoveFileSymbol)symbol); - break; - - case SymbolDefinitionType.ProgId: - this.AddSymbolDefaultly(symbol); - this.Data.EnsureTable(this.TableDefinitions["Extension"]); - break; - - case SymbolDefinitionType.Property: - this.AddPropertySymbol((PropertySymbol)symbol); - break; - - case SymbolDefinitionType.RemoveFile: - this.AddRemoveFileSymbol((RemoveFileSymbol)symbol); - break; - - case SymbolDefinitionType.Registry: - this.AddRegistrySymbol((RegistrySymbol)symbol); - break; - - case SymbolDefinitionType.RegLocator: - this.AddRegLocatorSymbol((RegLocatorSymbol)symbol); - break; - - case SymbolDefinitionType.RemoveRegistry: - this.AddRemoveRegistrySymbol((RemoveRegistrySymbol)symbol); - break; - - case SymbolDefinitionType.ServiceControl: - this.AddServiceControlSymbol((ServiceControlSymbol)symbol); - break; - - case SymbolDefinitionType.ServiceInstall: - this.AddServiceInstallSymbol((ServiceInstallSymbol)symbol); - break; - - case SymbolDefinitionType.Shortcut: - this.AddShortcutSymbol((ShortcutSymbol)symbol); - break; - - case SymbolDefinitionType.TextStyle: - this.AddTextStyleSymbol((TextStyleSymbol)symbol); - break; - - case SymbolDefinitionType.Upgrade: - this.AddUpgradeSymbol((UpgradeSymbol)symbol); - break; - - case SymbolDefinitionType.WixAction: - this.AddWixActionSymbol((WixActionSymbol)symbol); - break; - - case SymbolDefinitionType.WixCustomTableCell: - this.IndexCustomTableCellSymbol((WixCustomTableCellSymbol)symbol, cellsByTableAndRowId); - break; - - case SymbolDefinitionType.WixEnsureTable: - this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol); - break; - - case SymbolDefinitionType.WixPackage: - this.AddWixPackageSymbol((WixPackageSymbol)symbol); - break; - - // Symbols used internally and are not added to the output. - case SymbolDefinitionType.WixBuildInfo: - case SymbolDefinitionType.WixBindUpdatedFiles: - case SymbolDefinitionType.WixComponentGroup: - case SymbolDefinitionType.WixComplexReference: - case SymbolDefinitionType.WixDeltaPatchFile: - case SymbolDefinitionType.WixDeltaPatchSymbolPaths: - case SymbolDefinitionType.WixFragment: - case SymbolDefinitionType.WixFeatureGroup: - case SymbolDefinitionType.WixInstanceComponent: - case SymbolDefinitionType.WixInstanceTransforms: - case SymbolDefinitionType.WixFeatureModules: - case SymbolDefinitionType.WixGroup: - case SymbolDefinitionType.WixMediaTemplate: - case SymbolDefinitionType.WixMerge: - case SymbolDefinitionType.WixOrdering: - case SymbolDefinitionType.WixPatchBaseline: - case SymbolDefinitionType.WixPatchFamilyGroup: - case SymbolDefinitionType.WixPatch: - case SymbolDefinitionType.WixPatchRef: - case SymbolDefinitionType.WixPatchTarget: - case SymbolDefinitionType.WixProperty: - case SymbolDefinitionType.WixProductTag: - case SymbolDefinitionType.WixSimpleReference: - case SymbolDefinitionType.WixSuppressAction: - case SymbolDefinitionType.WixSuppressModularization: - case SymbolDefinitionType.WixUI: - case SymbolDefinitionType.WixVariable: - break; - - // Already processed by LoadTableDefinitions. - case SymbolDefinitionType.WixCustomTable: - case SymbolDefinitionType.WixCustomTableColumn: - break; - - case SymbolDefinitionType.MustBeFromAnExtension: - unknownSymbol = !this.AddSymbolFromExtension(symbol); - break; - - default: - unknownSymbol = !this.AddSymbolDefaultly(symbol); - break; - } - - if (unknownSymbol) - { - this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); - } - } - - this.AddIndexedCellSymbols(cellsByTableAndRowId); - this.EnsureRequiredTables(); - this.ReportGeneratedShortFileNameConflicts(); - this.ReportIllegalTables(); - this.ReportMismatchedModularizations(); - this.ReportWindowsInstallerDataInconsistencies(); - } - - private void AddAssemblySymbol(AssemblySymbol symbol) - { - var attributes = symbol.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; - - var row = this.CreateRow(symbol, "MsiAssembly"); - row[0] = symbol.ComponentRef; - row[1] = symbol.FeatureRef; - row[2] = symbol.ManifestFileRef; - row[3] = symbol.ApplicationFileRef; - row[4] = attributes; - } - - private void AddBBControlSymbol(BBControlSymbol symbol) - { - var attributes = symbol.Attributes; - attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; - attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; - attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; - attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; - attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; - attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; - attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; - attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; - - var row = this.CreateRow(symbol, "BBControl"); - row[0] = symbol.BillboardRef; - row[1] = symbol.BBControl; - row[2] = symbol.Type; - row[3] = symbol.X; - row[4] = symbol.Y; - row[5] = symbol.Width; - row[6] = symbol.Height; - row[7] = attributes; - row[8] = symbol.Text; - } - - private void AddClassSymbol(ClassSymbol symbol) - { - var row = this.CreateRow(symbol, "Class"); - row[0] = symbol.CLSID; - row[1] = symbol.Context; - row[2] = symbol.ComponentRef; - row[3] = symbol.DefaultProgIdRef; - row[4] = symbol.Description; - row[5] = symbol.AppIdRef; - row[6] = symbol.FileTypeMask; - row[7] = symbol.IconRef; - row[8] = symbol.IconIndex; - row[9] = symbol.DefInprocHandler; - row[10] = symbol.Argument; - row[11] = symbol.FeatureRef; - row[12] = symbol.RelativePath ? (int?)1 : null; - } - - private void AddControlSymbol(ControlSymbol symbol) - { - var text = symbol.Text; - var attributes = symbol.Attributes; - attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; - attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; - attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; - attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; - attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; - attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; - attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; - attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; - - // If we're tracking disk space, and this is a non-FormatSize Text control, - // and the text attribute starts with '[' and ends with ']', add a space. - // It is not necessary for the whole string to be a property, just those - // two characters matter. - if (symbol.TrackDiskSpace && - "Text" == symbol.Type && - WindowsInstallerConstants.MsidbControlAttributesFormatSize != (attributes & WindowsInstallerConstants.MsidbControlAttributesFormatSize) && - null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) - { - text = String.Concat(text, " "); - } - - var row = this.CreateRow(symbol, "Control"); - row[0] = symbol.DialogRef; - row[1] = symbol.Control; - row[2] = symbol.Type; - row[3] = symbol.X; - row[4] = symbol.Y; - row[5] = symbol.Width; - row[6] = symbol.Height; - row[7] = attributes; - row[8] = symbol.Property; - row[9] = text; - row[10] = symbol.NextControlRef; - row[11] = symbol.Help; - } - - private void AddControlEventSymbol(ControlEventSymbol symbol) - { - var row = this.CreateRow(symbol, "ControlEvent"); - row[0] = symbol.DialogRef; - row[1] = symbol.ControlRef; - row[2] = symbol.Event; - row[3] = symbol.Argument; - row[4] = String.IsNullOrEmpty(symbol.Condition) ? "1" : symbol.Condition; - row[5] = symbol.Ordering; - } - - private void AddComponentSymbol(ComponentSymbol symbol) - { - var attributes = ComponentLocation.Either == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0; - attributes |= ComponentLocation.SourceOnly == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0; - attributes |= ComponentKeyPathType.Registry == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0; - attributes |= ComponentKeyPathType.OdbcDataSource == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0; - attributes |= symbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0; - attributes |= symbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0; - attributes |= symbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0; - attributes |= symbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0; - attributes |= symbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0; - attributes |= symbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0; - attributes |= symbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; - attributes |= symbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; - - var row = this.CreateRow(symbol, "Component"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentId; - row[2] = symbol.DirectoryRef; - row[3] = attributes; - row[4] = symbol.Condition; - row[5] = symbol.KeyPath; - } - - private void AddCustomActionSymbol(CustomActionSymbol symbol) - { - var type = symbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; - type |= symbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; - type |= symbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0; - type |= symbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0; - type |= CustomActionExecutionType.FirstSequence == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0; - type |= CustomActionExecutionType.OncePerProcess == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0; - type |= CustomActionExecutionType.ClientRepeat == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0; - type |= CustomActionExecutionType.Deferred == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0; - type |= CustomActionExecutionType.Rollback == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0; - type |= CustomActionExecutionType.Commit == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0; - type |= CustomActionSourceType.File == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0; - type |= CustomActionSourceType.Directory == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0; - type |= CustomActionSourceType.Property == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0; - type |= CustomActionTargetType.Dll == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0; - type |= CustomActionTargetType.Exe == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0; - type |= CustomActionTargetType.TextData == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0; - type |= CustomActionTargetType.JScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0; - type |= CustomActionTargetType.VBScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0; - - if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) - { - type |= symbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; - type |= symbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; - } - - var row = this.CreateRow(symbol, "CustomAction"); - row[0] = symbol.Id.Id; - row[1] = type; - row[2] = symbol.Source; - row[3] = symbol.Target; - row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; - - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); - } - } - - private void AddDialogSymbol(DialogSymbol symbol) - { - var attributes = symbol.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; - attributes |= symbol.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; - attributes |= symbol.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; - attributes |= symbol.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; - attributes |= symbol.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; - attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; - attributes |= symbol.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; - attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; - attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; - attributes |= symbol.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; - attributes |= symbol.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; - - var row = this.CreateRow(symbol, "Dialog"); - row[0] = symbol.Id.Id; - row[1] = symbol.HCentering; - row[2] = symbol.VCentering; - row[3] = symbol.Width; - row[4] = symbol.Height; - row[5] = attributes; - row[6] = symbol.Title; - row[7] = symbol.FirstControlRef; - row[8] = symbol.DefaultControlRef; - row[9] = symbol.CancelControlRef; - - this.Data.EnsureTable(this.TableDefinitions["ListBox"]); - } - - private void AddDirectorySymbol(DirectorySymbol symbol) - { - (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); - - var shortName = symbol.ShortName; - var sourceShortname = symbol.SourceShortName; - - if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false)) - { - shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef); - } - - if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) - { - sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); - } - - var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName); - var targetName = CreateMsiFilename(shortName, name); - - if (String.IsNullOrEmpty(targetName)) - { - targetName = "."; - } - - var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; - - var row = this.CreateRow(symbol, "Directory"); - row[0] = symbol.Id.Id; - row[1] = parentDir; - row[2] = defaultDir; - - if (OutputType.Module == this.Data.Type) - { - var directoryId = symbol.Id.Id; - - if (WindowsInstallerStandard.IsStandardDirectory(directoryId)) - { - // If the directory table contains references to standard windows folders - // mergemod.dll will add customactions to set the MSM directory to - // the same directory as the standard windows folder and will add references to - // custom action to all the standard sequence tables. A problem will occur - // if the MSI does not have these tables as mergemod.dll does not add these - // tables to the MSI if absent. This code adds the tables in case mergemod.dll - // needs them. - this.Data.EnsureTable(this.TableDefinitions["CustomAction"]); - this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); - } - else - { - foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories()) - { - if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id)); - } - } - } - } - } - - private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol) - { - var name = symbol.DestinationName; - if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.DestinationShortName = this.CreateShortName(name, true, "CopyFile", symbol.ComponentRef, symbol.FileRef); - } - - var row = this.CreateRow(symbol, "DuplicateFile"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentRef; - row[2] = symbol.FileRef; - row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); - row[4] = symbol.DestinationFolder; - } - - private void AddEnvironmentSymbol(EnvironmentSymbol symbol) - { - var action = String.Empty; - var system = symbol.System ? "*" : String.Empty; - var uninstall = symbol.Permanent ? String.Empty : "-"; - var value = symbol.Value; - - switch (symbol.Action) - { - case EnvironmentActionType.Create: - action = "+"; - break; - case EnvironmentActionType.Set: - action = "="; - break; - case EnvironmentActionType.Remove: - action = "!"; - break; - } - - switch (symbol.Part) - { - case EnvironmentPartType.First: - value = String.Concat(value, symbol.Separator, "[~]"); - break; - case EnvironmentPartType.Last: - value = String.Concat("[~]", symbol.Separator, value); - break; - } - - var row = this.CreateRow(symbol, "Environment"); - row[0] = symbol.Id.Id; - row[1] = String.Concat(action, uninstall, system, symbol.Name); - row[2] = value; - row[3] = symbol.ComponentRef; - } - - private void AddErrorSymbol(ErrorSymbol symbol) - { - var row = this.CreateRow(symbol, "Error"); - row[0] = Convert.ToInt32(symbol.Id.Id); - row[1] = symbol.Message; - } - - private void AddFeatureSymbol(FeatureSymbol symbol) - { - var attributes = symbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0; - attributes |= symbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0; - attributes |= FeatureInstallDefault.FollowParent == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0; - attributes |= FeatureInstallDefault.Source == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0; - attributes |= FeatureTypicalDefault.Advertise == symbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0; - - var row = this.CreateRow(symbol, "Feature"); - row[0] = symbol.Id.Id; - row[1] = symbol.ParentFeatureRef; - row[2] = symbol.Title; - row[3] = symbol.Description; - row[4] = symbol.Display; - row[5] = symbol.Level; - row[6] = symbol.DirectoryRef; - row[7] = attributes; - } - - private void AddFileSymbol(FileSymbol symbol) - { - var name = symbol.Name; - if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortName = this.CreateShortName(name, true, "File", symbol.DirectoryRef); - - if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts)) - { - potentialConflicts = new List(); - this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts); - } - - potentialConflicts.Add(symbol); - } - - var row = (FileRow)this.CreateRow(symbol, "File"); - row.File = symbol.Id.Id; - row.Component = symbol.ComponentRef; - row.FileName = CreateMsiFilename(symbol.ShortName, name); - row.FileSize = symbol.FileSize; - row.Version = symbol.Version; - row.Language = symbol.Language; - row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here - row.Sequence = symbol.Sequence; - row.Source = symbol.Source.Path; - - var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Hidden) == FileSymbolAttributes.Hidden ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.ReadOnly) == FileSymbolAttributes.ReadOnly ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.System) == FileSymbolAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Vital) == FileSymbolAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; - row.Attributes = attributes; - - if (symbol.FontTitle != null) - { - var fontRow = this.CreateRow(symbol, "Font"); - fontRow[0] = symbol.Id.Id; - fontRow[1] = symbol.FontTitle; - } - - if (symbol.SelfRegCost.HasValue) - { - var selfRegRow = this.CreateRow(symbol, "SelfReg"); - selfRegRow[0] = symbol.Id.Id; - selfRegRow[1] = symbol.SelfRegCost.Value; - } - } - - private void AddIniFileSymbol(IniFileSymbol symbol) - { - var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile"; - - var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortFileName = this.CreateShortName(name, true, "IniFile", symbol.ComponentRef); - } - - var row = this.CreateRow(symbol, tableName); - row[0] = symbol.Id.Id; - row[1] = CreateMsiFilename(symbol.ShortFileName, name); - row[2] = symbol.DirProperty; - row[3] = symbol.Section; - row[4] = symbol.Key; - row[5] = symbol.Value; - row[6] = symbol.Action; - row[7] = symbol.ComponentRef; - } - - private void AddIniLocatorSymbol(IniLocatorSymbol symbol) - { - var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortFileName = this.CreateShortName(name, true, "IniFileSearch"); - } - - var row = this.CreateRow(symbol, "IniLocator"); - row[0] = symbol.Id.Id; - row[1] = CreateMsiFilename(symbol.ShortFileName, name); - row[2] = symbol.Section; - row[3] = symbol.Key; - row[4] = symbol.Field; - row[5] = symbol.Type; - } - - private void AddMediaSymbol(MediaSymbol symbol) - { - if (this.Section.Type != SectionType.Module) - { - var row = (MediaRow)this.CreateRow(symbol, "Media"); - row.DiskId = symbol.DiskId; - row.LastSequence = symbol.LastSequence ?? 0; - row.DiskPrompt = symbol.DiskPrompt; - row.Cabinet = symbol.Cabinet; - row.VolumeLabel = symbol.VolumeLabel; - row.Source = symbol.Source; - } - } - - private void AddModuleConfigurationSymbol(ModuleConfigurationSymbol symbol) - { - var row = this.CreateRow(symbol, "ModuleConfiguration"); - row[0] = symbol.Id.Id; - row[1] = symbol.Format; - row[2] = symbol.Type; - row[3] = symbol.ContextData; - row[4] = symbol.DefaultValue; - row[5] = (symbol.KeyNoOrphan ? WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan : 0) | - (symbol.NonNullable ? WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable : 0); - row[6] = symbol.DisplayName; - row[7] = symbol.Description; - row[8] = symbol.HelpLocation; - row[9] = symbol.HelpKeyword; - } - - private void AddMsiEmbeddedUISymbol(MsiEmbeddedUISymbol symbol) - { - var attributes = symbol.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0; - attributes |= symbol.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0; - - var row = this.CreateRow(symbol, "MsiEmbeddedUI"); - row[0] = symbol.Id.Id; - row[1] = symbol.FileName; - row[2] = attributes; - row[3] = symbol.MessageFilter; - row[4] = symbol.Source; - } - - private void AddMsiServiceConfigSymbol(MsiServiceConfigSymbol symbol) - { - var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; - events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; - events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; - - var row = this.CreateRow(symbol, "MsiServiceConfigFailureActions"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = events; - row[3] = symbol.ConfigType; - row[4] = symbol.Argument; - row[5] = symbol.ComponentRef; - } - - private void AddMsiServiceConfigFailureActionsSymbol(MsiServiceConfigFailureActionsSymbol symbol) - { - var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; - events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; - events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; - - var row = this.CreateRow(symbol, "MsiServiceConfig"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = events; - row[3] = symbol.ResetPeriod.HasValue ? symbol.ResetPeriod : null; - row[4] = symbol.RebootMessage ?? "[~]"; - row[5] = symbol.Command ?? "[~]"; - row[6] = symbol.Actions; - row[7] = symbol.DelayActions; - row[8] = symbol.ComponentRef; - } - - private void AddMoveFileSymbol(MoveFileSymbol symbol) - { - var name = symbol.DestinationName; - if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.DestinationShortName = this.CreateShortName(name, true, "MoveFile", symbol.ComponentRef); - } - - var row = this.CreateRow(symbol, "MoveFile"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentRef; - row[2] = symbol.SourceName; - row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); - row[4] = symbol.SourceFolder; - row[5] = symbol.DestFolder; - row[6] = symbol.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0; - } - - private void AddPropertySymbol(PropertySymbol symbol) - { - if (String.IsNullOrEmpty(symbol.Value)) - { - return; - } - - var row = (PropertyRow)this.CreateRow(symbol, "Property"); - row.Property = symbol.Id.Id; - row.Value = symbol.Value; - } - - private void AddRemoveFileSymbol(RemoveFileSymbol symbol) - { - var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortFileName = this.CreateShortName(name, true, "RemoveFile", symbol.ComponentRef); - } - - var installMode = symbol.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0; - installMode |= symbol.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0; - - var row = this.CreateRow(symbol, "RemoveFile"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentRef; - row[2] = CreateMsiFilename(symbol.ShortFileName, symbol.FileName); - row[3] = symbol.DirPropertyRef; - row[4] = installMode; - } - - private void AddRegistrySymbol(RegistrySymbol symbol) - { - var value = symbol.Value; - - switch (symbol.ValueType) - { - case RegistryValueType.Binary: - value = String.Concat("#x", value); - break; - case RegistryValueType.Expandable: - value = String.Concat("#%", value); - break; - case RegistryValueType.Integer: - value = String.Concat("#", value); - break; - case RegistryValueType.MultiString: - switch (symbol.ValueAction) - { - case RegistryValueActionType.Append: - value = String.Concat("[~]", value); - break; - case RegistryValueActionType.Prepend: - value = String.Concat(value, "[~]"); - break; - case RegistryValueActionType.Write: - default: - if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) - { - value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); - } - break; - } - break; - case RegistryValueType.String: - // escape the leading '#' character for string registry keys - if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - { - value = String.Concat("#", value); - } - break; - } - - var row = this.CreateRow(symbol, "Registry"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[4] = value; - row[5] = symbol.ComponentRef; - } - - private void AddRegLocatorSymbol(RegLocatorSymbol symbol) - { - var type = (int)symbol.Type; - type |= symbol.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0; - - var row = this.CreateRow(symbol, "RegLocator"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[4] = type; - } - - private void AddRemoveRegistrySymbol(RemoveRegistrySymbol symbol) - { - if (symbol.Action == RemoveRegistryActionType.RemoveOnInstall) - { - var row = this.CreateRow(symbol, "RemoveRegistry"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[4] = symbol.ComponentRef; - } - else // Registry table is used to remove registry keys on uninstall. - { - var row = this.CreateRow(symbol, "Registry"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[5] = symbol.ComponentRef; - } - } - - private void AddServiceControlSymbol(ServiceControlSymbol symbol) - { - var events = symbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0; - events |= symbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0; - events |= symbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0; - events |= symbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0; - events |= symbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0; - events |= symbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0; - - var row = this.CreateRow(symbol, "ServiceControl"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = events; - row[3] = symbol.Arguments; - if (symbol.Wait.HasValue) - { - row[4] = symbol.Wait.Value ? 1 : 0; - } - row[5] = symbol.ComponentRef; - } - - private void AddServiceInstallSymbol(ServiceInstallSymbol symbol) - { - var errorControl = (int)symbol.ErrorControl; - errorControl |= symbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0; - - var serviceType = (int)symbol.ServiceType; - serviceType |= symbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0; - - var row = this.CreateRow(symbol, "ServiceInstall"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = symbol.DisplayName; - row[3] = serviceType; - row[4] = (int)symbol.StartType; - row[5] = errorControl; - row[6] = symbol.LoadOrderGroup; - row[7] = symbol.Dependencies; - row[8] = symbol.StartName; - row[9] = symbol.Password; - row[10] = symbol.Arguments; - row[11] = symbol.ComponentRef; - row[12] = symbol.Description; - } - - private void AddShortcutSymbol(ShortcutSymbol symbol) - { - var name = symbol.Name; - if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortName = this.CreateShortName(name, true, "Shortcut", symbol.ComponentRef, symbol.DirectoryRef); - } - - var row = this.CreateRow(symbol, "Shortcut"); - row[0] = symbol.Id.Id; - row[1] = symbol.DirectoryRef; - row[2] = CreateMsiFilename(symbol.ShortName, name); - row[3] = symbol.ComponentRef; - row[4] = symbol.Target; - row[5] = symbol.Arguments; - row[6] = symbol.Description; - row[7] = symbol.Hotkey; - row[8] = symbol.IconRef; - row[9] = symbol.IconIndex; - row[10] = (int?)symbol.Show; - row[11] = symbol.WorkingDirectory; - row[12] = symbol.DisplayResourceDll; - row[13] = symbol.DisplayResourceId; - row[14] = symbol.DescriptionResourceDll; - row[15] = symbol.DescriptionResourceId; - } - - private void AddTextStyleSymbol(TextStyleSymbol symbol) - { - var styleBits = symbol.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; - styleBits |= symbol.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0; - styleBits |= symbol.Strike ? WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike : 0; - styleBits |= symbol.Underline ? WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline : 0; - - long? color = null; - - if (symbol.Red.HasValue || symbol.Green.HasValue || symbol.Blue.HasValue) - { - color = symbol.Red ?? 0; - color += (long)(symbol.Green ?? 0) * 256; - color += (long)(symbol.Blue ?? 0) * 65536; - } - - var row = this.CreateRow(symbol, "TextStyle"); - row[0] = symbol.Id.Id; - row[1] = symbol.FaceName; - row[2] = symbol.Size; - row[3] = color; - row[4] = styleBits == 0 ? null : (int?)styleBits; - } - - private void AddUpgradeSymbol(UpgradeSymbol symbol) - { - var row = (UpgradeRow)this.CreateRow(symbol, "Upgrade"); - row.UpgradeCode = symbol.UpgradeCode; - row.VersionMin = symbol.VersionMin; - row.VersionMax = symbol.VersionMax; - row.Language = symbol.Language; - row.Remove = symbol.Remove; - row.ActionProperty = symbol.ActionProperty; - - var attributes = symbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0; - attributes |= symbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0; - attributes |= symbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0; - attributes |= symbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0; - attributes |= symbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0; - attributes |= symbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0; - row.Attributes = attributes; - } - - private void AddWixActionSymbol(WixActionSymbol symbol) - { - // Get the table definition for the action (and ensure the proper table exists for a module). - string sequenceTableName = null; - switch (symbol.SequenceTable) - { - case SequenceTable.AdminExecuteSequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - sequenceTableName = "ModuleAdminExecuteSequence"; - } - else - { - sequenceTableName = "AdminExecuteSequence"; - } - break; - case SequenceTable.AdminUISequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); - sequenceTableName = "ModuleAdminUISequence"; - } - else - { - sequenceTableName = "AdminUISequence"; - } - break; - case SequenceTable.AdvertiseExecuteSequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - sequenceTableName = "ModuleAdvtExecuteSequence"; - } - else - { - sequenceTableName = "AdvtExecuteSequence"; - } - break; - case SequenceTable.InstallExecuteSequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - sequenceTableName = "ModuleInstallExecuteSequence"; - } - else - { - sequenceTableName = "InstallExecuteSequence"; - } - break; - case SequenceTable.InstallUISequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); - sequenceTableName = "ModuleInstallUISequence"; - } - else - { - sequenceTableName = "InstallUISequence"; - } - break; - } - - // create the action sequence row in the output - var row = this.CreateRow(symbol, sequenceTableName); - - if (SectionType.Module == this.Section.Type) - { - row[0] = symbol.Action; - if (0 != symbol.Sequence) - { - row[1] = symbol.Sequence; - } - else - { - var after = (null == symbol.Before); - row[2] = after ? symbol.After : symbol.Before; - row[3] = after ? 1 : 0; - } - row[4] = symbol.Condition; - } - else - { - row[0] = symbol.Action; - row[1] = symbol.Condition; - row[2] = symbol.Sequence; - } - } - - private void IndexCustomTableCellSymbol(WixCustomTableCellSymbol wixCustomTableCellSymbol, Dictionary> cellsByTableAndRowId) - { - var tableAndRowId = wixCustomTableCellSymbol.TableRef + "/" + wixCustomTableCellSymbol.RowId; - if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) - { - cells = new List(); - cellsByTableAndRowId.Add(tableAndRowId, cells); - } - - cells.Add(wixCustomTableCellSymbol); - } - - private void AddIndexedCellSymbols(Dictionary> cellsByTableAndRowId) - { - foreach (var rowOfCells in cellsByTableAndRowId.Values) - { - var firstCellSymbol = rowOfCells[0]; - var customTableDefinition = this.TableDefinitions[firstCellSymbol.TableRef]; - - if (customTableDefinition.Unreal) - { - continue; - } - - var customRow = this.CreateRow(firstCellSymbol, customTableDefinition); - var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); - -#if TODO // SectionId seems like a good thing to preserve. - customRow.SectionId = symbol.SectionId; -#endif - foreach (var cell in rowOfCells) - { - var data = cell.Data; - - if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) - { - if (!String.IsNullOrEmpty(data)) - { - if (rowField.Column.Type == ColumnType.Number) - { - try - { - rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); - } - catch (OverflowException) - { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); - } - } - else if (rowField.Column.Category == ColumnCategory.Identifier) - { - if (this.BackendHelper.IsValidIdentifier(data) || this.BackendHelper.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) - { - rowField.Data = data; - } - else - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); - } - } - else - { - rowField.Data = data; - } - } - } - else - { - this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); - } - } - - for (var i = 0; i < customTableDefinition.Columns.Length; ++i) - { - if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) - { - this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellSymbol.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); - } - } - } - } - - private void AddWixEnsureTableSymbol(WixEnsureTableSymbol symbol) - { - var tableDefinition = this.TableDefinitions[symbol.Table]; - this.Data.EnsureTable(tableDefinition); - } - - private void AddWixPackageSymbol(WixPackageSymbol symbol) - { - // TODO: Remove the following from the compiler and do it here instead. - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); - //if (null != upgradeCode) - //{ - // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); - //} - - //if (isPerMachine) - //{ - // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); - //} - } - - private bool AddSymbolFromExtension(IntermediateSymbol symbol) - { - foreach (var extension in this.BackendExtensions) - { - if (extension.TryProcessSymbol(this.Section, symbol, this.Data, this.TableDefinitions)) - { - return true; - } - } - - return false; - } - - private bool AddSymbolDefaultly(IntermediateSymbol symbol) => - this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions); - - private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable) - { - var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"]; - var table = this.Data.EnsureTable(tableDefinition); - if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable)) - { - var row = this.CreateRow(symbol, tableDefinition); - row[0] = ignoredTable; - } - } - - private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) - { - var directory = symbol.Name.Trim(PathSeparatorChars); - var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); - - var start = 0; - var end = directory.IndexOfAny(PathSeparatorChars); - var path = String.Empty; - - while (start <= end) - { - var subdirectoryName = directory.Substring(start, end - start); - - if (!String.IsNullOrEmpty(subdirectoryName)) - { - path = Path.Combine(path, subdirectoryName); - - var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); - var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); - - var subdirectoryRow = this.CreateRow(symbol, "Directory"); - subdirectoryRow[0] = id; - subdirectoryRow[1] = parentDir; - subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName); - - parentDir = id; - } - - start = end + 1; - end = symbol.Name.IndexOfAny(PathSeparatorChars, start); - } - - var name = (start == 0) ? directory : directory.Substring(start); - - return (name, parentDir); - } - - private void EnsureRequiredTables() - { - // check for missing table and add them or display an error as appropriate - switch (this.Data.Type) - { - case OutputType.Module: - this.Data.EnsureTable(this.TableDefinitions["Component"]); - this.Data.EnsureTable(this.TableDefinitions["Directory"]); - this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]); - this.Data.EnsureTable(this.TableDefinitions["File"]); - this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]); - this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]); - break; - - case OutputType.PatchCreation: - var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0; - var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0; - var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0; - - if (imageFamiliesCount < 1) - { - this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies")); - } - - if (targetImagesCount < 1) - { - this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages")); - } - - if (upgradedImagesCount < 1) - { - this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages")); - } - - this.Data.EnsureTable(this.TableDefinitions["Properties"]); - break; - - case OutputType.Product: - this.Data.EnsureTable(this.TableDefinitions["File"]); - this.Data.EnsureTable(this.TableDefinitions["Media"]); - break; - } - } - - private void ReportGeneratedShortFileNameConflicts() - { - foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1)) - { - this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName)); - for (var i = 1; i < conflicts.Count; ++i) - { - this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers)); - } - } - } - - private void ReportIllegalTables() - { - foreach (var table in this.Data.Tables) - { - switch (this.Data.Type) - { - case OutputType.Module: - if ("BBControl" == table.Name || - "Billboard" == table.Name || - "CCPSearch" == table.Name || - "Feature" == table.Name || - "LaunchCondition" == table.Name || - "Media" == table.Name || - "Patch" == table.Name || - "Upgrade" == table.Name || - "WixMerge" == table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); - } - } - else if ("Error" == table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); - } - } - break; - - case OutputType.PatchCreation: - if (!table.Definition.Unreal && - "_SummaryInformation" != table.Name && - "ExternalFiles" != table.Name && - "FamilyFileRanges" != table.Name && - "ImageFamilies" != table.Name && - "PatchMetadata" != table.Name && - "PatchSequence" != table.Name && - "Properties" != table.Name && - "TargetFiles_OptionalData" != table.Name && - "TargetImages" != table.Name && - "UpgradedFiles_OptionalData" != table.Name && - "UpgradedFilesToIgnore" != table.Name && - "UpgradedImages" != table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); - } - } - break; - - case OutputType.Patch: - if (!table.Definition.Unreal && - "_SummaryInformation" != table.Name && - "Media" != table.Name && - "MsiFileHash" != table.Name && - "MsiPatchMetadata" != table.Name && - "MsiPatchSequence" != table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); - } - } - break; - - case OutputType.Product: - if ("ModuleAdminExecuteSequence" == table.Name || - "ModuleAdminUISequence" == table.Name || - "ModuleAdvtExecuteSequence" == table.Name || - "ModuleAdvtUISequence" == table.Name || - "ModuleComponents" == table.Name || - "ModuleConfiguration" == table.Name || - "ModuleDependency" == table.Name || - "ModuleExclusion" == table.Name || - "ModuleIgnoreTable" == table.Name || - "ModuleInstallExecuteSequence" == table.Name || - "ModuleInstallUISequence" == table.Name || - "ModuleSignature" == table.Name || - "ModuleSubstitution" == table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); - } - } - break; - } - } - } - - private void ReportMismatchedModularizations() - { - // verify that modularization types match for foreign key relationships - foreach (var tableDefinition in this.TableDefinitions) - { - foreach (var columnDefinition in tableDefinition.Columns) - { - if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue) - { - if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition)) - { - var keyColumnIndex = columnDefinition.KeyColumn ?? -1; - - if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length) - { - this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex)); - } - else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) - { - this.Messaging.Write(WarningMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString())); - } - } - // else - ignore missing table definitions as that error is caught in other places - } - } - } - } - - private void ReportWindowsInstallerDataInconsistencies() - { - // Get the output's minimum installer version - var outputInstallerVersion = Int32.MaxValue; - - if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable)) - { - outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue; - } - - // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40). - if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["Error"]); - } - - // Check for the presence of tables/rows/columns that require MSI 1.1 or later. - if (outputInstallerVersion < 110) - { - if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable)) - { - foreach (var row in isolatedComponentTable.Rows) - { - this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); - } - } - } - - // Check for the presence of tables/rows/columns that require MSI 4.0 or later - if (outputInstallerVersion < 400) - { - if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable)) - { - foreach (var row in shortcutTable.Rows) - { - if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) - { - this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); - } - } - } - } - } - - private static OutputType SectionTypeToOutputType(SectionType type) - { - switch (type) - { - case SectionType.Bundle: - return OutputType.Bundle; - case SectionType.Module: - return OutputType.Module; - case SectionType.Product: - return OutputType.Product; - case SectionType.PatchCreation: - return OutputType.PatchCreation; - case SectionType.Patch: - return OutputType.Patch; - - default: - throw new ArgumentOutOfRangeException(nameof(type)); - } - } - - private Row CreateRow(IntermediateSymbol symbol, string tableDefinitionName) => - this.CreateRow(symbol, this.TableDefinitions[tableDefinitionName]); - - private Row CreateRow(IntermediateSymbol symbol, TableDefinition tableDefinition) => - this.BackendHelper.CreateRow(this.Section, symbol, this.Data, tableDefinition); - - - private string CreateShortName(string longName, bool keepExtension, params string[] args) - { - longName = longName.ToLowerInvariant(); - - // collect all the data - var strings = new List(1 + args.Length); - strings.Add(longName); - strings.AddRange(args); - - // prepare for hashing - var stringData = String.Join("|", strings); - var data = Encoding.UTF8.GetBytes(stringData); - - // hash the data - byte[] hash; - using (var sha1 = new SHA1CryptoServiceProvider()) - { - hash = sha1.ComputeHash(data); - } - - // generate the short file/directory name without an extension - var shortName = new StringBuilder(Convert.ToBase64String(hash)); - shortName.Length = 8; - shortName.Replace('+', '-').Replace('/', '_'); - - if (keepExtension) - { - var extension = Path.GetExtension(longName); - - if (4 < extension.Length) - { - extension = extension.Substring(0, 4); - } - - shortName.Append(extension); - - // check the generated short name to ensure its still legal (the extension may not be legal) - if (!this.BackendHelper.IsValidShortFilename(shortName.ToString(), false)) - { - // remove the extension (by truncating the generated file name back to the generated characters) - shortName.Length -= extension.Length; - } - } - - return shortName.ToString().ToLowerInvariant(); - } - - private static string CreateMsiFilename(string shortName, string longName) - { - if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase)) - { - return longName; - } - else - { - return shortName + "|" + longName; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs deleted file mode 100644 index 7c1e085c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using WixToolset.Core.Native; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.Native.Msm; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Retrieve files information and extract them from merge modules. - /// - internal class ExtractMergeModuleFilesCommand - { - public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable wixMergeSymbols, IEnumerable fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.WixMergeSymbols = wixMergeSymbols; - this.FileFacades = fileFacades; - this.OutputInstallerVersion = installerVersion; - this.IntermediateFolder = intermediateFolder; - this.SuppressLayout = suppressLayout; - } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - private IEnumerable WixMergeSymbols { get; } - - private IEnumerable FileFacades { get; } - - private int OutputInstallerVersion { get; } - - private string IntermediateFolder { get; } - - private bool SuppressLayout { get; } - - public IEnumerable MergeModulesFileFacades { get; private set; } - - public void Execute() - { - var mergeModulesFileFacades = new List(); - - var 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. - var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); - - foreach (var wixMergeRow in this.WixMergeSymbols) - { - var 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(WixMergeSymbol wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) - { - var containsFiles = false; - - try - { - // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. - using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) - { - if (db.TableExists("File") && db.TableExists("Component")) - { - var uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (var 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) - foreach (var record in view.Records) - { - // 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. - var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1])); - fileSymbol.Attributes = wixMergeRow.FileAttributes; - fileSymbol.DirectoryRef = record[2]; - fileSymbol.DiskId = wixMergeRow.DiskId; - fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; - - var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - - // If case-sensitive collision with another merge module or a user-authored file identifier. - if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) - { - this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id)); - } - else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module - { - this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id)); - } - else // no collision - { - mergeModulesFileFacades.Add(mergeModuleFileFacade); - - // Keep updating the indexes as new rows are added. - indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); - uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); - } - - containsFiles = true; - } - } - } - - // Get the summary information to detect the Schema - using (var summaryInformation = new SummaryInformation(db)) - { - var moduleInstallerVersionString = summaryInformation.GetProperty(14); - - try - { - var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); - if (moduleInstallerVersion > this.OutputInstallerVersion) - { - this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion)); - } - } - catch (FormatException) - { - throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); - } - } - } - } - catch (FileNotFoundException) - { - throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); - } - catch (Win32Exception) - { - throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile)); - } - - return containsFiles; - } - - private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow) - { - var moduleOpen = false; - short mergeLanguage; - - var mergeId = wixMergeRow.Id.Id; - - try - { - mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString())); - return; - } - - try - { - merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); - moduleOpen = true; - - // extract the module cabinet, then explode all of the files to a temp directory - var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); - merge.ExtractCAB(moduleCabPath); - - var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); - Directory.CreateDirectory(mergeIdPath); - - try - { - var cabinet = new Cabinet(moduleCabPath); - cabinet.Extract(mergeIdPath); - } - catch (FileNotFoundException) - { - throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); - } - catch - { - throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); - } - } - catch (COMException ce) - { - throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); - } - finally - { - if (moduleOpen) - { - merge.CloseModule(); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs deleted file mode 100644 index fe65ccef..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Extensibility; - - internal class FileSystemManager - { - public FileSystemManager(IEnumerable fileSystemExtensions) - { - this.Extensions = fileSystemExtensions; - } - - private IEnumerable Extensions { get; } - - public bool CompareFiles(string firstPath, string secondPath) - { - foreach (var extension in this.Extensions) - { - var compared = extension.CompareFiles(firstPath, secondPath); - if (compared.HasValue) - { - return compared.Value; - } - } - - return BuiltinCompareFiles(firstPath, secondPath); - } - - private static bool BuiltinCompareFiles(string firstPath, string secondPath) - { - if (String.Equals(firstPath, secondPath, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - using (var firstStream = File.OpenRead(firstPath)) - using (var secondStream = File.OpenRead(secondPath)) - { - if (firstStream.Length != secondStream.Length) - { - return false; - } - - // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance. - // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined. - var firstBuffer = new byte[16 * 1024]; - var secondBuffer = new byte[16 * 1024]; - - var firstReadLength = 0; - do - { - firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length); - var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length); - - if (firstReadLength != secondReadLength) - { - return false; - } - - for (var i = 0; i < firstReadLength; ++i) - { - if (firstBuffer[i] != secondBuffer[i]) - { - return false; - } - } - } while (0 < firstReadLength); - } - - return true; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs deleted file mode 100644 index 3cdc0c28..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Set the guids for components with generatable guids and validate all are appropriately unique. - /// - internal class FinalizeComponentGuids - { - internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) - { - this.Messaging = messaging; - this.BackendHelper = helper; - this.PathResolver = pathResolver; - this.Section = section; - this.Platform = platform; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - private Dictionary ComponentIdGenSeeds { get; set; } - - private ILookup FilesByComponentId { get; set; } - - private Dictionary RegistrySymbolsById { get; set; } - - private Dictionary TargetPathsByDirectoryId { get; set; } - - public void Execute() - { - var componentGuidConditions = new Dictionary>(StringComparer.OrdinalIgnoreCase); - var guidCollisions = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var componentSymbol in this.Section.Symbols.OfType()) - { - if (componentSymbol.ComponentId == "*") - { - this.GenerateComponentGuid(componentSymbol); - } - - // Now check for GUID collisions, but we don't care about unmanaged components and - // if there's a * GUID remaining, there's already an error that explained why it - // was not replaced with a real GUID. - if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") - { - if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) - { - components = new List(); - componentGuidConditions.Add(componentSymbol.ComponentId, components); - } - - components.Add(componentSymbol); - if (components.Count > 1) - { - guidCollisions.Add(componentSymbol.ComponentId); - } - } - } - - if (guidCollisions.Count > 0) - { - this.ReportGuidCollisions(guidCollisions, componentGuidConditions); - } - } - - private void GenerateComponentGuid(ComponentSymbol componentSymbol) - { - if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) - { - this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); - return; - } - - if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) - { - if (this.RegistrySymbolsById is null) - { - this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) - { - var bitness = componentSymbol.Win64 ? "64" : String.Empty; - var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); - } - } - 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 (this.TargetPathsByDirectoryId is null) - { - this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); - } - - // If the component id generation seeds have not been indexed - // from the Directory symbols do that now. - if (this.ComponentIdGenSeeds is null) - { - // If there are any Directory symbols, build up the Component Guid - // generation seeds indexed by Directory/@Id. - this.ComponentIdGenSeeds = this.Section.Symbols.OfType() - .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) - .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); - } - - // If the file symbols have not been indexed by File's ComponentRef yet - // then do that now. - if (this.FilesByComponentId is null) - { - this.FilesByComponentId = this.Section.Symbols.OfType().ToLookup(f => f.ComponentRef); - } - - // validate component meets all the conditions to have a generated guid - var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; - var numFilesInComponent = currentComponentFiles.Count(); - string path = null; - - foreach (var fileSymbol in currentComponentFiles) - { - if (fileSymbol.Id.Id == componentSymbol.KeyPath) - { - // calculate the key file's canonical target path - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); - var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); - 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)) - { - this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); - } - - // if component has more than one file, the key path must be versioned - if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.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(fileSymbol.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); - } - } - } - - // if the rules were followed, reward with a generated guid - if (!this.Messaging.EncounteredError) - { - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); - } - } - } - - private void ReportGuidCollisions(HashSet guidCollisions, Dictionary> componentGuidConditions) - { - Dictionary fileSymbolsById = null; - - foreach (var guid in guidCollisions) - { - var collidingComponents = componentGuidConditions[guid]; - var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); - - foreach (var componentSymbol in collidingComponents) - { - string path; - string type; - - if (componentSymbol.KeyPathType == ComponentKeyPathType.File) - { - if (fileSymbolsById is null) - { - fileSymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; - type = "source path"; - } - else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) - { - if (this.RegistrySymbolsById is null) - { - this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; - type = "registry path"; - } - else - { - if (this.TargetPathsByDirectoryId is null) - { - this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); - } - - path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); - type = "directory"; - } - - if (allComponentsHaveConditions) - { - this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); - } - } - } - } - - private Dictionary ResolveDirectoryTargetPaths() - { - var directories = this.Section.Symbols.OfType().ToList(); - - var targetPathsByDirectoryId = new Dictionary(directories.Count); - - // Get the target paths for all directories. - foreach (var directory in directories) - { - // 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 (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) - { - continue; - } - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); - } - - return targetPathsByDirectoryId; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs deleted file mode 100644 index b8cca752..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ /dev/null @@ -1,408 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using System.Text; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class GenerateDatabaseCommand - { - private const string IdtsSubFolder = "_idts"; - - public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.FileSystemManager = fileSystemManager; - this.Data = data; - this.OutputPath = outputPath; - this.TableDefinitions = tableDefinitions; - this.IntermediateFolder = intermediateFolder; - this.KeepAddedColumns = keepAddedColumns; - this.SuppressAddingValidationRows = suppressAddingValidationRows; - this.UseSubDirectory = useSubdirectory; - } - - private IBackendHelper BackendHelper { get; } - - private FileSystemManager FileSystemManager { get; } - - /// - /// Whether to keep columns added in a transform. - /// - private bool KeepAddedColumns { get; } - - private IMessaging Messaging { get; } - - private WindowsInstallerData Data { get; } - - private string OutputPath { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private string IntermediateFolder { get; } - - public List GeneratedTemporaryFiles { get; } = new List(); - - /// - /// Whether to use a subdirectory based on the database file name for intermediate files. - /// - private bool SuppressAddingValidationRows { get; } - - private bool UseSubDirectory { get; } - - public void Execute() - { - // Add the _Validation rows. - if (!this.SuppressAddingValidationRows) - { - this.AddValidationRows(); - } - - var baseDirectory = this.IntermediateFolder; - - if (this.UseSubDirectory) - { - var filename = Path.GetFileNameWithoutExtension(this.OutputPath); - baseDirectory = Path.Combine(baseDirectory, filename); - } - - var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); - - var type = OpenDatabase.CreateDirect; - - if (OutputType.Patch == this.Data.Type) - { - type |= OpenDatabase.OpenPatchFile; - } - - try - { - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); - - Directory.CreateDirectory(idtFolder); - - using (var db = new Database(this.OutputPath, type)) - { - // 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.Data.Codepage) - { - this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder); - } - - this.ImportTables(db, idtFolder); - - // Insert substorages (usually transforms inside a patch or instance transforms in a package). - this.ImportSubStorages(db); - - // We're good, commit the changes to the new database. - db.Commit(); - } - } - catch (IOException e) - { - // TODO: this error message doesn't seem specific enough - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); - } - } - - private void AddValidationRows() - { - var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]); - - // Add the validation rows for real tables and columns. - foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) - { - foreach (var columnDef in table.Definition.Columns.Where(c => !c.Unreal)) - { - var row = validationTable.CreateRow(null); - - row[0] = table.Name; - - row[1] = columnDef.Name; - - if (columnDef.Nullable) - { - row[2] = "Y"; - } - else - { - row[2] = "N"; - } - - if (columnDef.MinValue.HasValue) - { - row[3] = columnDef.MinValue.Value; - } - - if (columnDef.MaxValue.HasValue) - { - row[4] = columnDef.MaxValue.Value; - } - - row[5] = columnDef.KeyTable; - - if (columnDef.KeyColumn.HasValue) - { - row[6] = columnDef.KeyColumn.Value; - } - - if (ColumnCategory.Unknown != columnDef.Category) - { - row[7] = columnDef.Category.ToString(); - } - - row[8] = columnDef.Possibilities; - - row[9] = columnDef.Description; - } - } - } - - private void ImportTables(Database db, string idtDirectory) - { - foreach (var table in this.Data.Tables) - { - var importTable = table; - var 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.Data.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 (var columnDefinition in table.Definition.Columns) - { - if (ColumnType.Object == columnDefinition.Type) - { - importTable = new Table(table.Definition); - hasBinaryColumn = true; - break; - } - } - - // Create the table via IDT import. - if ("_Streams" != importTable.Name) - { - try - { - var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns); - command.Execute(); - - var trackIdt = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); - this.GeneratedTemporaryFiles.Add(trackIdt); - - db.Import(command.IdtPath); - } - 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) - { - var query = new StringBuilder("SELECT "); - - // Build the query for the view. - var firstColumn = true; - foreach (var columnDefinition in table.Definition.Columns) - { - if (columnDefinition.Unreal) - { - continue; - } - - if (!firstColumn) - { - query.Append(","); - } - - query.AppendFormat(" `{0}`", columnDefinition.Name); - firstColumn = false; - } - query.AppendFormat(" FROM `{0}`", table.Name); - - using (var tableView = db.OpenExecuteView(query.ToString())) - { - // Import each row containing a stream - foreach (var row in table.Rows) - { - using (var record = new Record(table.Definition.Columns.Length)) - { - // Stream names are created by concatenating the name of the table with the values - // of the primary key (delimited by periods). - var streamName = new StringBuilder(); - - // the _Streams table doesn't prepend the table name (or a period) - if ("_Streams" != table.Name) - { - streamName.Append(table.Name); - } - - var needStream = false; - - for (var i = 0; i < table.Definition.Columns.Length; i++) - { - var columnDefinition = table.Definition.Columns[i]; - - if (columnDefinition.Unreal) - { - continue; - } - - switch (columnDefinition.Type) - { - case ColumnType.Localized: - case ColumnType.Preserved: - case ColumnType.String: - var str = row.FieldAsString(i); - - if (columnDefinition.PrimaryKey) - { - if (0 < streamName.Length) - { - streamName.Append("."); - } - - streamName.Append(str); - } - - record.SetString(i + 1, str); - break; - case ColumnType.Number: - record.SetInteger(i + 1, row.FieldAsInteger(i)); - break; - - case ColumnType.Object: - var path = row.FieldAsString(i); - if (null != path) - { - needStream = true; - try - { - record.SetStream(i + 1, path); - } - catch (Win32Exception e) - { - if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME - { - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); - } - else - { - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); - } - } - } - break; - } - } - - // check for a stream name that is more than 62 characters long (the maximum allowed length) - if (needStream && Database.MsiMaxStreamNameLength < streamName.Length) - { - this.Messaging.Write(ErrorMessages.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(); - } - } - } - } - - private void ImportSubStorages(Database db) - { - if (0 < this.Data.SubStorages.Count) - { - using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) - { - foreach (var subStorage in this.Data.SubStorages) - { - var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); - - // Bind the transform. - var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); - command.Execute(); - - if (this.Messaging.EncounteredError) - { - continue; - } - - // Add the storage to the database. - using (var record = new Record(2)) - { - record.SetString(1, subStorage.Name); - record.SetStream(2, transformFile); - storagesView.Modify(ModifyView.Assign, record); - } - } - } - } - } - - private void SetDatabaseCodepage(Database db, int codepage, string idtFolder) - { - // Write out the _ForceCodepage IDT file. - var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt"); - using (var 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"); - } - - var trackIdt = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary); - this.GeneratedTemporaryFiles.Add(trackIdt); - - // Try to import the table into the MSI. - try - { - db.Import(idtPath); - } - catch (WixInvalidIdtException) - { - // The IDT should always be generated correctly, so an invalid code page was given. - throw new WixException(ErrorMessages.IllegalCodepage(codepage)); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs deleted file mode 100644 index faa03762..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs +++ /dev/null @@ -1,582 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - /// - /// Creates a transform by diffing two outputs. - /// - internal class GenerateTransformCommand - { - private const char sectionDelimiter = '/'; - private readonly IMessaging messaging; - private SummaryInformationStreams transformSummaryInfo; - - /// - /// Instantiates a new Differ class. - /// - public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) - { - this.messaging = messaging; - this.TargetOutput = targetOutput; - this.UpdatedOutput = updatedOutput; - this.PreserveUnchangedRows = preserveUnchangedRows; - this.ShowPedanticMessages = showPedanticMessages; - } - - private WindowsInstallerData TargetOutput { get; } - - private WindowsInstallerData UpdatedOutput { get; } - - private TransformFlags ValidationFlags { get; } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - private bool ShowPedanticMessages { get; } - - /// - /// Gets or sets the option to suppress keeping special rows. - /// - /// The option to suppress keeping special rows. - private bool SuppressKeepingSpecialRows { get; } - - /// - /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. - /// - /// The option to keep all rows including unchanged rows. - private bool PreserveUnchangedRows { get; } - - public WindowsInstallerData Transform { get; private set; } - - /// - /// Creates a transform by diffing two outputs. - /// - public WindowsInstallerData Execute() - { - var targetOutput = this.TargetOutput; - var updatedOutput = this.UpdatedOutput; - var validationFlags = this.ValidationFlags; - - var transform = new WindowsInstallerData(null) - { - Type = OutputType.Transform, - Codepage = updatedOutput.Codepage - }; - - this.transformSummaryInfo = new SummaryInformationStreams(); - - // compare the codepages - if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); - if (null != updatedOutput.SourceLineNumbers) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); - } - } - - // compare the output types - if (targetOutput.Type != updatedOutput.Type) - { - throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); - } - - // compare the contents of the tables - foreach (var targetTable in targetOutput.Tables) - { - var updatedTable = updatedOutput.Tables[targetTable.Name]; - var operation = TableOperation.None; - - var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); - - if (TableOperation.Drop == operation) - { - var droppedTable = transform.EnsureTable(targetTable.Definition); - droppedTable.Operation = TableOperation.Drop; - } - else if (TableOperation.None == operation) - { - var modifiedTable = transform.EnsureTable(updatedTable.Definition); - foreach (var row in rows) - { - modifiedTable.Rows.Add(row); - } - } - } - - // added tables - foreach (var updatedTable in updatedOutput.Tables) - { - if (null == targetOutput.Tables[updatedTable.Name]) - { - var addedTable = transform.EnsureTable(updatedTable.Definition); - addedTable.Operation = TableOperation.Add; - - foreach (var updatedRow in updatedTable.Rows) - { - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - addedTable.Rows.Add(updatedRow); - } - } - } - - // set summary information properties - if (!this.SuppressKeepingSpecialRows) - { - var summaryInfoTable = transform.Tables["_SummaryInformation"]; - this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); - } - - this.Transform = transform; - return this.Transform; - } - - /// - /// Add a row to the using the primary key. - /// - /// The indexed rows. - /// The row to index. - private void AddIndexedRow(Dictionary index, Row row) - { - var primaryKey = row.GetPrimaryKey(); - - if (null != primaryKey) - { - if (index.TryGetValue(primaryKey, out var collisionRow)) - { -#if TODO_PATCH // This case doesn't seem like it can happen any longer. - // Overriding WixActionRows have a primary key defined and take precedence in the index. - if (row is WixActionRow actionRow) - { - // If the current row is not overridable, see if the indexed row is. - if (!actionRow.Overridable) - { - if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) - { - // The indexed key is overridable and should be replaced. - index[primaryKey] = actionRow; - } - } - - // If we got this far, the row does not need to be indexed. - return; - } -#endif - - if (this.ShowPedanticMessages) - { - this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); - } - } - else - { - index.Add(primaryKey, row); - } - } - 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 bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow) - { - comparedRow = null; - - var keepRow = false; - - if (null == targetRow ^ null == updatedRow) - { - if (null == targetRow) - { - updatedRow.Operation = RowOperation.Add; - comparedRow = updatedRow; - } - else if (null == updatedRow) - { - targetRow.Operation = RowOperation.Delete; - 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; - } - } - else - { - if (this.PreserveUnchangedRows) - { - keepRow = true; - } - - for (var i = 0; i < updatedRow.Fields.Length; i++) - { - var columnDefinition = updatedRow.Fields[i].Column; - - if (!columnDefinition.PrimaryKey) - { - var 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 = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i)); - } - } - else if (ColumnType.Preserved == columnDefinition.Type) - { - updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); - - // keep rows containing preserved fields so the historical data is available to the binder - keepRow = !this.SuppressKeepingSpecialRows; - } - else if (ColumnType.Object == columnDefinition.Type) - { - var targetObjectField = (ObjectField)targetRow.Fields[i]; - var 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 = targetObjectField.UnresolvedData; - - // keep rows containing object fields so the files can be compared in the binder - keepRow = !this.SuppressKeepingSpecialRows; - } - else - { - modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i)); - } - - if (modified) - { - if (null != updatedRow.Fields[i].PreviousData) - { - updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); - } - - updatedRow.Fields[i].Modified = true; - updatedRow.Operation = RowOperation.Modify; - keepRow = true; - } - } - } - - if (keepRow) - { - comparedRow = updatedRow; - comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; - } - } - } - - return keepRow; - } - - private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) - { - var 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 - { - var updatedPrimaryKeys = new Dictionary(); - var targetPrimaryKeys = new Dictionary(); - - // compare the table definitions - if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) - { - // continue to the next table; may be more mismatches - this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); - } - else - { - this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); - - // diff the target and updated rows - foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) - { - var targetPrimaryKey = targetPrimaryKeyEntry.Key; - var targetRow = targetPrimaryKeyEntry.Value; - updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow); - - var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared); - - if (keepRow) - { - rows.Add(compared); - } - } - - // find the inserted rows - foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys) - { - var updatedPrimaryKey = updatedPrimaryKeyEntry.Key; - - if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey)) - { - var updatedRow = updatedPrimaryKeyEntry.Value; - - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - rows.Add(updatedRow); - } - } - } - } - - return rows; - } - - private void IndexPrimaryKeys(Table targetTable, Dictionary targetPrimaryKeys, Table updatedTable, Dictionary updatedPrimaryKeys) - { - // index the target rows - foreach (var row in targetTable.Rows) - { - this.AddIndexedRow(targetPrimaryKeys, row); - - if ("Property" == targetTable.Name) - { - var id = row.FieldAsString(0); - - if ("ProductCode" == id) - { - this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1); - - if ("*" == this.transformSummaryInfo.TargetProductCode) - { - this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == id) - { - this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1); - } - else if ("UpgradeCode" == id) - { - this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1); - } - } - else if ("_SummaryInformation" == targetTable.Name) - { - var id = row.FieldAsInteger(0); - - if (1 == id) // PID_CODEPAGE - { - this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1); - } - else if (7 == id) // PID_TEMPLATE - { - this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1); - } - else if (14 == id) // PID_PAGECOUNT - { - this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1); - } - } - } - - // index the updated rows - foreach (var row in updatedTable.Rows) - { - this.AddIndexedRow(updatedPrimaryKeys, row); - - if ("Property" == updatedTable.Name) - { - var id = row.FieldAsString(0); - - if ("ProductCode" == id) - { - this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1); - - if ("*" == this.transformSummaryInfo.UpdatedProductCode) - { - this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == id) - { - this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1); - } - } - else if ("_SummaryInformation" == updatedTable.Name) - { - var id = row.FieldAsInteger(0); - - if (1 == id) // PID_CODEPAGE - { - this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1); - } - else if (7 == id) // PID_TEMPLATE - { - this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1); - } - else if (14 == id) // PID_PAGECOUNT - { - this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1); - } - } - } - } - - private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) - { - // calculate the minimum version of MSI required to process the transform - var minimumVersion = 100; - - if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin)) - { - minimumVersion = Math.Max(targetMin, updatedMin); - } - - var summaryRows = new Dictionary(summaryInfoTable.Rows.Count); - - foreach (var row in summaryInfoTable.Rows) - { - var id = row.FieldAsInteger(0); - - summaryRows[id] = row; - - if ((int)SummaryInformation.Transform.CodePage == id) - { - row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; - row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id) - { - row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id) - { - row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.ProductCodes == id) - { - 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 == id) - { - row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Security == id) - { - row[1] = "4"; - } - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; - summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) - { - var 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/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs deleted file mode 100644 index 949d5e18..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class GetFileFacadesCommand - { - public GetFileFacadesCommand(IntermediateSection section, IWindowsInstallerBackendHelper backendHelper) - { - this.Section = section; - this.BackendHelper = backendHelper; - } - - private IntermediateSection Section { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - public List FileFacades { get; private set; } - - public void Execute() - { - var facades = new List(); - - var assemblyFile = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); -#if TODO_PATCHING_DELTA - //var deltaPatchFiles = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); -#endif - - foreach (var file in this.Section.Symbols.OfType()) - { - assemblyFile.TryGetValue(file.Id.Id, out var assembly); - -#if TODO_PATCHING_DELTA - //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); - // TODO: should we be passing along delta information to the file facade? Probably, right? -#endif - var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); - - facades.Add(fileFacade); - } - -#if TODO_PATCHING_DELTA - this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); -#endif - - this.FileFacades = facades; - } - -#if TODO_PATCHING_DELTA - /// - /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. - /// - public void ResolveDeltaPatchSymbolPaths(Dictionary deltaPatchFiles, IEnumerable facades) - { - ILookup filesByComponent = null; - ILookup filesByDirectory = null; - ILookup filesByDiskId = null; - - foreach (var row in this.Section.Symbols.OfType().OrderBy(r => r.SymbolType)) - { - switch (row.SymbolType) - { - case SymbolPathType.File: - this.MergeSymbolPaths(row, deltaPatchFiles[row.SymbolId]); - break; - - case SymbolPathType.Component: - if (null == filesByComponent) - { - filesByComponent = facades.ToLookup(f => f.File.ComponentRef); - } - - foreach (var facade in filesByComponent[row.SymbolId]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); - } - break; - - case SymbolPathType.Directory: - if (null == filesByDirectory) - { - filesByDirectory = facades.ToLookup(f => f.File.DirectoryRef); - } - - foreach (var facade in filesByDirectory[row.SymbolId]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); - } - break; - - case SymbolPathType.Media: - if (null == filesByDiskId) - { - filesByDiskId = facades.ToLookup(f => f.File.DiskId.ToString(CultureInfo.InvariantCulture)); - } - - foreach (var facade in filesByDiskId[row.SymbolId]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); - } - break; - - case SymbolPathType.Product: - foreach (var fileRow in deltaPatchFiles.Values) - { - this.MergeSymbolPaths(row, fileRow); - } - break; - - default: - // error - break; - } - } - } - - /// - /// 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(WixDeltaPatchSymbolPathsSymbol row, WixDeltaPatchFileSymbol file) - { - if (file.SymbolPaths is null) - { - file.SymbolPaths = row.SymbolPaths; - } - else - { - file.SymbolPaths = String.Concat(file.SymbolPaths, ";", 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); - } - } - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs deleted file mode 100644 index 2ac563ac..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class GetFileFacadesFromTransforms - { - public GetFileFacadesFromTransforms(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, FileSystemManager fileSystemManager, IEnumerable subStorages) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.FileSystemManager = fileSystemManager; - this.SubStorages = subStorages; - } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - private FileSystemManager FileSystemManager { get; } - - private IEnumerable SubStorages { get; } - - public List FileFacades { get; private set; } - - public void Execute() - { - var allFileRows = new List(); - - var patchMediaFileRows = new Dictionary>(); - - //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); - - // Index paired transforms by name without their "#" prefix. - var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); - - // Enumerate through main transforms. - foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) - { - var mainTransform = substorage.Data; - var mainFileTable = mainTransform.Tables["File"]; - - if (null == mainFileTable) - { - continue; - } - - // Index File table of pairedTransform - var pairedTransform = pairedTransforms["#" + substorage.Name]; - var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - - foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) - { - var mainFileId = mainFileRow.File; - - // We need compare the underlying files and include all file changes. - var objectField = (ObjectField)mainFileRow.Fields[9]; - var pairedFileRow = pairedFileRows.Get(mainFileId); - - // If the file is new, we always need to add it to the patch. - if (mainFileRow.Operation == RowOperation.Add) - { - if (null != pairedFileRow) // RowOperation.Add - { - // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = RowOperation.Add; - } - } - else - { - // 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.FileSystemManager.CompareFiles(objectField.PreviousData, 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 |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~WindowsInstallerConstants.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 (var field in mainFileRow.Fields) - { - field.Modified = false; - } - - if (null != pairedFileRow) - { - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = false; - pairedFileRow.Operation = RowOperation.None; - } - continue; - } - } - } - - // index patch files by diskId+fileId - var diskId = mainFileRow.DiskId; - - if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) - { - mediaFileRows = new RowDictionary(); - patchMediaFileRows.Add(diskId, mediaFileRows); - } - - var patchFileRow = mediaFileRows.Get(mainFileId); - - if (null == patchFileRow) - { - //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); - patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); - mainFileRow.CopyTo(patchFileRow); - - mediaFileRows.Add(patchFileRow); - -#if TODO_PATCHING_DELTA - // TODO: should we be passing along delta information to the file facade? Probably, right? -#endif - var fileFacade = this.BackendHelper.CreateFileFacade(patchFileRow); - - allFileRows.Add(fileFacade); - } - 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, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source)); - } - -#if TODO_PATCHING_DELTA - // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. - patchFileRow.AppendPreviousDataFrom(mainFileRow); -#endif - } - } - } - - this.FileFacades = allFileRows; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs deleted file mode 100644 index 2eb95bc5..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class LoadTableDefinitionsCommand - { - public LoadTableDefinitionsCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions) - { - this.Messaging = messaging; - this.Section = section; - this.BackendExtensions = backendExtensions; - } - - public IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IEnumerable BackendExtensions { get; } - - public TableDefinitionCollection TableDefinitions { get; private set; } - - public TableDefinitionCollection Execute() - { - var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - var customColumnsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - if (customColumnsById.Any()) - { - foreach (var symbol in this.Section.Symbols.OfType()) - { - var customTableDefinition = this.CreateCustomTable(symbol, customColumnsById); - tableDefinitions.Add(customTableDefinition); - } - } - - foreach (var backendExtension in this.BackendExtensions) - { - foreach (var tableDefinition in backendExtension.TableDefinitions) - { - if (tableDefinitions.Contains(tableDefinition.Name)) - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(backendExtension.GetType().Assembly.Location, tableDefinition.Name)); - } - - tableDefinitions.Add(tableDefinition); - } - } - - this.TableDefinitions = tableDefinitions; - return this.TableDefinitions; - } - - private TableDefinition CreateCustomTable(WixCustomTableSymbol symbol, Dictionary customColumnsById) - { - var columnNames = symbol.ColumnNamesSeparated; - var columns = new List(columnNames.Length); - - foreach (var name in columnNames) - { - var column = customColumnsById[symbol.Id.Id + "/" + name]; - - var type = ColumnType.Unknown; - - if (column.Type == IntermediateFieldType.String) - { - type = column.Localizable ? ColumnType.Localized : ColumnType.String; - } - else if (column.Type == IntermediateFieldType.Number) - { - type = ColumnType.Number; - } - else if (column.Type == IntermediateFieldType.Path) - { - type = ColumnType.Object; - } - - var category = ColumnCategory.Unknown; - switch (column.Category) - { - case WixCustomTableColumnCategoryType.Text: - category = ColumnCategory.Text; - break; - case WixCustomTableColumnCategoryType.UpperCase: - category = ColumnCategory.UpperCase; - break; - case WixCustomTableColumnCategoryType.LowerCase: - category = ColumnCategory.LowerCase; - break; - case WixCustomTableColumnCategoryType.Integer: - category = ColumnCategory.Integer; - break; - case WixCustomTableColumnCategoryType.DoubleInteger: - category = ColumnCategory.DoubleInteger; - break; - case WixCustomTableColumnCategoryType.TimeDate: - category = ColumnCategory.TimeDate; - break; - case WixCustomTableColumnCategoryType.Identifier: - category = ColumnCategory.Identifier; - break; - case WixCustomTableColumnCategoryType.Property: - category = ColumnCategory.Property; - break; - case WixCustomTableColumnCategoryType.Filename: - category = ColumnCategory.Filename; - break; - case WixCustomTableColumnCategoryType.WildCardFilename: - category = ColumnCategory.WildCardFilename; - break; - case WixCustomTableColumnCategoryType.Path: - category = ColumnCategory.Path; - break; - case WixCustomTableColumnCategoryType.Paths: - category = ColumnCategory.Paths; - break; - case WixCustomTableColumnCategoryType.AnyPath: - category = ColumnCategory.AnyPath; - break; - case WixCustomTableColumnCategoryType.DefaultDir: - category = ColumnCategory.DefaultDir; - break; - case WixCustomTableColumnCategoryType.RegPath: - category = ColumnCategory.RegPath; - break; - case WixCustomTableColumnCategoryType.Formatted: - category = ColumnCategory.Formatted; - break; - case WixCustomTableColumnCategoryType.FormattedSddl: - category = ColumnCategory.FormattedSDDLText; - break; - case WixCustomTableColumnCategoryType.Template: - category = ColumnCategory.Template; - break; - case WixCustomTableColumnCategoryType.Condition: - category = ColumnCategory.Condition; - break; - case WixCustomTableColumnCategoryType.Guid: - category = ColumnCategory.Guid; - break; - case WixCustomTableColumnCategoryType.Version: - category = ColumnCategory.Version; - break; - case WixCustomTableColumnCategoryType.Language: - category = ColumnCategory.Language; - break; - case WixCustomTableColumnCategoryType.Binary: - category = ColumnCategory.Binary; - break; - case WixCustomTableColumnCategoryType.CustomSource: - category = ColumnCategory.CustomSource; - break; - case WixCustomTableColumnCategoryType.Cabinet: - category = ColumnCategory.Cabinet; - break; - case WixCustomTableColumnCategoryType.Shortcut: - category = ColumnCategory.Shortcut; - break; - case null: - default: - break; - } - - var modularization = ColumnModularizeType.None; - - switch (column.Modularize) - { - case null: - case WixCustomTableColumnModularizeType.None: - modularization = ColumnModularizeType.None; - break; - case WixCustomTableColumnModularizeType.Column: - modularization = ColumnModularizeType.Column; - break; - case WixCustomTableColumnModularizeType.CompanionFile: - modularization = ColumnModularizeType.CompanionFile; - break; - case WixCustomTableColumnModularizeType.Condition: - modularization = ColumnModularizeType.Condition; - break; - case WixCustomTableColumnModularizeType.ControlEventArgument: - modularization = ColumnModularizeType.ControlEventArgument; - break; - case WixCustomTableColumnModularizeType.ControlText: - modularization = ColumnModularizeType.ControlText; - break; - case WixCustomTableColumnModularizeType.Icon: - modularization = ColumnModularizeType.Icon; - break; - case WixCustomTableColumnModularizeType.Property: - modularization = ColumnModularizeType.Property; - break; - case WixCustomTableColumnModularizeType.SemicolonDelimited: - modularization = ColumnModularizeType.SemicolonDelimited; - break; - } - - var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal); - columns.Add(columnDefinition); - } - - var customTable = new TableDefinition(symbol.Id.Id, null, columns, symbol.Unreal); - return customTable; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs deleted file mode 100644 index 6446692e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.Native.Msm; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Merge modules into the database at output path. - /// - internal class MergeModulesCommand - { - public MergeModulesCommand(IMessaging messaging, IEnumerable fileFacades, IntermediateSection section, IEnumerable suppressedTableNames, string outputPath, string intermediateFolder) - { - this.Messaging = messaging; - this.FileFacades = fileFacades; - this.Section = section; - this.SuppressedTableNames = suppressedTableNames ?? Array.Empty(); - this.OutputPath = outputPath; - this.IntermediateFolder = intermediateFolder; - } - - private IMessaging Messaging { get; } - - private IEnumerable FileFacades { get; } - - private IntermediateSection Section { get; } - - private IEnumerable SuppressedTableNames { get; } - - private string OutputPath { get; } - - private string IntermediateFolder { get; } - - public void Execute() - { - var wixMergeSymbols = this.Section.Symbols.OfType().ToList(); - if (!wixMergeSymbols.Any()) - { - return; - } - - IMsmMerge2 merge = null; - var commit = true; - var logOpen = false; - var databaseOpen = false; - var logPath = Path.Combine(this.IntermediateFolder, "merge.log"); - - try - { - merge = MsmInterop.GetMsmMerge(); - - merge.OpenLog(logPath); - logOpen = true; - - merge.OpenDatabase(this.OutputPath); - databaseOpen = true; - - var featureModulesByMergeId = this.Section.Symbols.OfType().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); - - // process all the merge rows - foreach (var wixMergeRow in wixMergeSymbols) - { - var moduleOpen = false; - - try - { - short mergeLanguage; - - try - { - mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.Language.ToString())); - continue; - } - - this.Messaging.Write(VerboseMessages.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. - this.Messaging.Write(VerboseMessages.MergingMergeModule(wixMergeRow.SourceFile)); - merge.MergeEx(wixMergeRow.FeatureRef, wixMergeRow.DirectoryRef, callback); - - // Connect any non-primary features. - if (featureModulesByMergeId.TryGetValue(wixMergeRow.Id.Id, out var featureModules)) - { - foreach (var featureModule in featureModules) - { - this.Messaging.Write(VerboseMessages.ConnectingMergeModule(wixMergeRow.SourceFile, featureModule.FeatureRef)); - merge.Connect(featureModule.FeatureRef); - } - } - } - catch (COMException) - { - commit = false; - } - finally - { - var mergeErrors = merge.Errors; - - // display all the errors encountered during the merge operations for this module - for (var i = 1; i <= mergeErrors.Count; i++) - { - var mergeError = mergeErrors[i]; - var databaseKeys = new StringBuilder(); - var moduleKeys = new StringBuilder(); - - // build a string of the database keys - for (var 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 (var 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: - this.Messaging.Write(ErrorMessages.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleKeys.ToString())); - break; - case MsmErrorType.msmErrorFeatureRequired: - this.Messaging.Write(ErrorMessages.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id.Id)); - break; - case MsmErrorType.msmErrorLanguageFailed: - this.Messaging.Write(ErrorMessages.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorLanguageUnsupported: - this.Messaging.Write(ErrorMessages.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorResequenceMerge: - this.Messaging.Write(WarningMessages.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorTableMerge: - if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table - { - this.Messaging.Write(WarningMessages.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); - } - break; - case MsmErrorType.msmErrorPlatformMismatch: - this.Messaging.Write(ErrorMessages.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); - break; - default: - this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "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}'", Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); - break; - } - } - - if (0 >= mergeErrors.Count && !commit) - { - this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'", 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 (this.Messaging.EncounteredError) - { - return; - } - - using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) - { - // Suppress individual actions. - foreach (var suppressAction in this.Section.Symbols.OfType()) - { - var tableName = suppressAction.SequenceTable.WindowsInstallerTableName(); - if (db.TableExists(tableName)) - { - var query = $"SELECT * FROM {tableName} WHERE `Action` = '{suppressAction.Action}'"; - - using (var view = db.OpenExecuteView(query)) - using (var record = view.Fetch()) - { - if (null != record) - { - this.Messaging.Write(WarningMessages.SuppressMergedAction(suppressAction.Action, tableName)); - view.Modify(ModifyView.Delete, record); - } - } - } - } - - // Query for merge module actions in suppressed sequences and drop them. - foreach (var tableName in this.SuppressedTableNames) - { - if (!db.TableExists(tableName)) - { - continue; - } - - using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) - { - foreach (var resultRecord in view.Records) - { - this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName)); - } - } - - // drop suppressed sequences - using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) - { - } - - // delete the validation rows - using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) - using (var record = new Record(1)) - { - record.SetString(1, tableName); - view.Execute(record); - } - } - - // now update the Attributes column for the files from the Merge Modules - this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); - using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) - { - foreach (var file in this.FileFacades) - { - if (!file.FromModule) - { - continue; - } - - using (var record = new Record(1)) - { - record.SetString(1, file.Id); - view.Execute(record); - } - - using (var 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.Sequence); - - // Update the file attributes to match the compression specified - // on the Merge element or on the Package element. - var attributes = 0; - - // Get the current value if its not null. - if (!recordUpdate.IsNull(2)) - { - attributes = recordUpdate.GetInteger(2); - } - - if (file.Compressed) - { - attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed; - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - } - else if (file.Uncompressed) - { - attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; - } - else // clear all compression bits. - { - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - } - - recordUpdate.SetInteger(2, attributes); - - view.Modify(ModifyView.Update, recordUpdate); - } - } - } - - db.Commit(); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs deleted file mode 100644 index 04f1b771..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class ModularizeCommand - { - public ModularizeCommand(IBackendHelper backendHelper, WindowsInstallerData output, string modularizationSuffix, IEnumerable suppressSymbols) - { - this.BackendHelper = backendHelper; - this.Output = output; - this.ModularizationSuffix = modularizationSuffix; - - // Gather all the unique suppress modularization identifiers. - this.SuppressModularizationIdentifiers = new HashSet(suppressSymbols.Select(s => s.SuppressIdentifier)); - } - - private IBackendHelper BackendHelper { get; } - - private WindowsInstallerData Output { get; } - - private string ModularizationSuffix { get; } - - private HashSet SuppressModularizationIdentifiers { get; } - - public void Execute() - { - foreach (var table in this.Output.Tables) - { - this.ModularizeTable(table); - } - } - - private void ModularizeTable(Table table) - { - var modularizedColumns = new List(); - - // find the modularized columns - for (var i = 0; i < table.Definition.Columns.Length; ++i) - { - if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) - { - modularizedColumns.Add(i); - } - } - - if (0 < modularizedColumns.Count) - { - foreach (var row in table.Rows) - { - foreach (var modularizedColumn in modularizedColumns) - { - var field = row.Fields[modularizedColumn]; - - if (field.Data != null) - { - field.Data = this.ModularizedRowFieldValue(row, field); - } - } - } - } - } - - private string ModularizedRowFieldValue(Row row, Field field) - { - var fieldData = field.AsString(); - - if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) - { - var modularizeType = field.Column.ModularizeType; - - // special logic for the ControlEvent table's Argument column - // this column requires different modularization methods depending upon the value of the Event column - if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) - { - switch (row[2].ToString()) - { - case "CheckExistingTargetPath": // redirectable property name - case "CheckTargetPath": - case "DoAction": // custom action name - case "NewDialog": // dialog name - case "SelectionBrowse": - case "SetTargetPath": - case "SpawnDialog": - case "SpawnWaitDialog": - if (this.BackendHelper.IsValidIdentifier(fieldData)) - { - modularizeType = ColumnModularizeType.Column; - } - else - { - modularizeType = ColumnModularizeType.Property; - } - break; - default: // formatted - modularizeType = ColumnModularizeType.Property; - break; - } - } - else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) - { - // icons are stored in the Binary table, so they get column-type modularization - if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && this.BackendHelper.IsValidIdentifier(fieldData)) - { - modularizeType = ColumnModularizeType.Column; - } - else - { - modularizeType = ColumnModularizeType.Property; - } - } - - switch (modularizeType) - { - case ColumnModularizeType.Column: - // ensure the value is an identifier (otherwise it shouldn't be modularized this way) - if (!this.BackendHelper.IsValidIdentifier(fieldData)) - { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); - } - - // if we're not supposed to suppress modularization of this identifier - if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) - { - fieldData = String.Concat(fieldData, this.ModularizationSuffix); - } - break; - - case ColumnModularizeType.Property: - case ColumnModularizeType.Condition: - Regex regex; - if (ColumnModularizeType.Property == modularizeType) - { - regex = new Regex(@"\[(?[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); - } - else - { - Debug.Assert(ColumnModularizeType.Condition == modularizeType); - - // This heinous looking regular expression is actually quite an elegant way - // to shred the entire condition into the identifiers that need to be - // modularized. Let's break it down piece by piece: - // - // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the - // regular expression is case insensitive so we don't have to worry about - // all the permutations of these strings. - // 2. Look for quoted strings. Quoted strings are just text and are ignored - // outright. - // 3. Look for environment variables. These look like identifiers we might - // otherwise be interested in but start with a percent sign. Like quoted - // strings these enviroment variable references are ignored outright. - // 4. Match all identifiers that are things that need to be modularized. Note - // the special characters (!, $, ?, &) that denote Component and Feature states. - regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); - - // less performant version of the above with captures showing where everything lives - // regex = new Regex(@"(?NOT|EQV|XOR|OR|AND|IMP)|(?"".*?"")|(?%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); - } - - var matches = regex.Matches(fieldData); - - var sb = new StringBuilder(fieldData); - - // Notice how this code walks backward through the list - // because it modifies the string as we through it. - for (var i = matches.Count - 1; 0 <= i; i--) - { - var group = matches[i].Groups["identifier"]; - if (group.Success) - { - var identifier = group.Value; - if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) - { - sb.Insert(group.Index + group.Length, this.ModularizationSuffix); - } - } - } - - fieldData = sb.ToString(); - break; - - case ColumnModularizeType.CompanionFile: - // if we're not supposed to ignore this identifier and the value does not start with - // a digit, we must have a companion file so modularize it - if (!this.SuppressModularizationIdentifiers.Contains(fieldData) && - 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) - { - fieldData = String.Concat(fieldData, this.ModularizationSuffix); - } - break; - - case ColumnModularizeType.Icon: - if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) - { - var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); - if (-1 == start) - { - fieldData = String.Concat(fieldData, this.ModularizationSuffix); - } - else - { - fieldData = String.Concat(fieldData.Substring(0, start), this.ModularizationSuffix, fieldData.Substring(start)); - } - } - break; - - case ColumnModularizeType.SemicolonDelimited: - var keys = fieldData.Split(';'); - for (var i = 0; i < keys.Length; ++i) - { - if (!String.IsNullOrEmpty(keys[i])) - { - keys[i] = String.Concat(keys[i], this.ModularizationSuffix); - } - } - - fieldData = String.Join(";", keys); - break; - } - } - - return fieldData; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs deleted file mode 100644 index 5dd4d3ea..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class OptimizeFileFacadesOrderCommand - { - public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List fileFacades) - { - this.BackendHelper = helper; - this.PathResolver = pathResolver; - this.Section = section; - this.Platform = platform; - this.FileFacades = fileFacades; - } - - public List FileFacades { get; private set; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - public List Execute() - { - var canonicalComponentTargetPaths = this.ComponentTargetPaths(); - - this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module)); - - return this.FileFacades; - } - - private Dictionary ComponentTargetPaths() - { - var directories = this.ResolveDirectories(); - - var canonicalPathsByDirectoryId = new Dictionary(); - foreach (var component in this.Section.Symbols.OfType()) - { - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform); - canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath); - } - - return canonicalPathsByDirectoryId; - } - - private Dictionary ResolveDirectories() - { - var targetPathsByDirectoryId = new Dictionary(); - - // Get the target paths for all directories. - foreach (var directory in this.Section.Symbols.OfType()) - { - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); - } - - return targetPathsByDirectoryId; - } - - private class FileFacadeOptimizer : IComparer - { - public FileFacadeOptimizer(Dictionary componentTargetPaths, bool optimizingMergeModule) - { - this.ComponentTargetPaths = componentTargetPaths; - this.OptimizingMergeModule = optimizingMergeModule; - } - - private Dictionary ComponentTargetPaths { get; } - - private bool OptimizingMergeModule { get; } - - public int Compare(IFileFacade x, IFileFacade y) - { - // First group files by DiskId but ignore if processing a Merge Module - // because Merge Modules don't have separate disks. - var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId); - - if (compare != 0) - { - return compare; - } - - // Next try to group files by target install directory. - if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) && - this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY)) - { - compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal); - - if (compare != 0) - { - return compare; - } - } - - // TODO: Consider sorting these facades even smarter by file size or file extension - // or other creative ideas to get optimal install speed out of MSI. - compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal); - - if (compare != 0) - { - return compare; - } - - return String.Compare(x.Id, y.Id, StringComparison.Ordinal); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs deleted file mode 100644 index 4d849753..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using WixToolset.Data.WindowsInstaller; - - internal class PatchTransform - { - public PatchTransform(string baseline, WindowsInstallerData transform) - { - this.Baseline = baseline; - this.Transform = transform; - } - - public string Baseline { get; } - - public WindowsInstallerData Transform { get; } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs deleted file mode 100644 index 1bd2a427..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class ProcessDependencyReferencesCommand - { - // The root registry key for the dependency extension. We write to Software\Classes explicitly - // based on the current security context instead of HKCR. See - // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. - private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; - private const string RegistryDependents = "Dependents"; - - public ProcessDependencyReferencesCommand(IBackendHelper backendHelper, IntermediateSection section, IEnumerable dependencyRefSymbols) - { - this.BackendHelper = backendHelper; - this.Section = section; - this.DependencyRefSymbols = dependencyRefSymbols; - } - - private IBackendHelper BackendHelper { get; } - - private IntermediateSection Section { get; } - - private IEnumerable DependencyRefSymbols { get; } - - public void Execute() - { - var wixDependencyRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); - var wixDependencyProviderRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); - - // For each relationship, get the provides and requires rows to generate registry values. - foreach (var wixDependencyRefRow in this.DependencyRefSymbols) - { - var providesId = wixDependencyRefRow.WixDependencyProviderRef; - var requiresId = wixDependencyRefRow.WixDependencyRef; - - // If we do not find both symbols, skip the registry key generation. - if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow)) - { - continue; - } - - if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow)) - { - continue; - } - - // Format the root registry key using the required provider key and the current provider key. - var requiresKey = wixDependencyRow.Id.Id; - var providesKey = wixDependencyRow.ProviderKey; - var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey); - - // Get the component ID from the provider. - var componentId = wixDependencyProviderRow.ParentRef; - - var id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "(Default)"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "*", - }); - - if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion)) - { - id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MinVersion"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "MinVersion", - Value = wixDependencyRow.MinVersion - }); - } - - var maxVersion = (string)wixDependencyRow[3]; - if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion)) - { - id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "MaxVersion", - Value = wixDependencyRow.MaxVersion - }); - } - - if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None) - { - id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "Attributes"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "Attributes", - Value = String.Concat("#", (int)wixDependencyRow.Attributes) - }); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs deleted file mode 100644 index 9a068603..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class ProcessPackageSoftwareTagsCommand - { - public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags, string intermediateFolder) - { - this.Section = section; - this.SoftwareTags = softwareTags; - this.IntermediateFolder = intermediateFolder; - } - - private string IntermediateFolder { get; } - - private IntermediateSection Section { get; } - - private IEnumerable SoftwareTags { get; } - - public void Execute() - { - string productName = null; - string productVersion = null; - string manufacturer = null; - string upgradeCode = null; - - var summaryInfo = this.Section.Symbols.OfType().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); - var packageCode = NormalizeGuid(summaryInfo?.Value); - - foreach (var property in this.Section.Symbols.OfType()) - { - switch (property.Id.Id) - { - case "ProductName": - productName = property.Value; - break; - case "ProductVersion": - productVersion = property.Value; - break; - case "Manufacturer": - manufacturer = property.Value; - break; - case "UpgradeCode": - upgradeCode = NormalizeGuid(property.Value); - break; - } - } - - var fileSymbolsById = this.Section.Symbols.OfType().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); - - var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); - - Directory.CreateDirectory(workingFolder); - - foreach (var tagRow in this.SoftwareTags) - { - if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) - { - var uniqueId = String.Concat("msi:package/", packageCode); - var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); - - // Write the tag file. - fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; - - using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) - { - CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); - } - - // Ensure the matching "SoftwareIdentificationTag" row exists and - // is populated correctly. - this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) - { - FileRef = fileSymbol.Id.Id, - Regid = tagRow.Regid, - TagId = uniqueId, - PersistentId = persistentId - }); - } - } - } - - private static string NormalizeGuid(string guidString) - { - if (Guid.TryParse(guidString, out var guid)) - { - return guid.ToString("D").ToUpperInvariant(); - } - - return guidString; - } - - private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) - { - var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; - - using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) - { - writer.WriteStartDocument(); - writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); - writer.WriteAttributeString("tagId", uniqueId); - writer.WriteAttributeString("name", name); - writer.WriteAttributeString("version", version); - writer.WriteAttributeString("versionScheme", versionScheme); - - writer.WriteStartElement("Entity"); - writer.WriteAttributeString("name", manufacturer); - writer.WriteAttributeString("regid", regid); - writer.WriteAttributeString("role", "softwareCreator tagCreator"); - writer.WriteEndElement(); // - - if (!String.IsNullOrEmpty(persistendId)) - { - writer.WriteStartElement("Meta"); - writer.WriteAttributeString("persistentId", persistendId); - writer.WriteEndElement(); // - } - - writer.WriteEndElement(); // - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs deleted file mode 100644 index 217609be..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class ProcessPropertiesCommand - { - public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper) - { - this.Section = section; - this.PackageSymbol = packageSymbol; - this.FallbackLcid = fallbackLcid; - this.PopulateDelayedVariables = populateDelayedVariables; - this.BackendHelper = backendHelper; - } - - private IntermediateSection Section { get; } - - private WixPackageSymbol PackageSymbol { get; } - - private int FallbackLcid { get; } - - private bool PopulateDelayedVariables { get; } - - private IBackendHelper BackendHelper { get; } - - public Dictionary DelayedVariablesCache { get; private set; } - - public string ProductLanguage { get; private set; } - - public void Execute() - { - PropertySymbol languageSymbol = null; - var variableCache = this.PopulateDelayedVariables ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null; - - if (SectionType.Product == this.Section.Type || variableCache != null) - { - foreach (var propertySymbol in this.Section.Symbols.OfType()) - { - // Set the ProductCode if it is to be generated. - if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) - { - propertySymbol.Value = this.BackendHelper.CreateGuid(); - -#if TODO_PATCHING // Is this still necessary? - // Update the target ProductCode in any instance transforms. - foreach (SubStorage subStorage in this.Output.SubStorages) - { - Output subStorageOutput = subStorage.Data; - if (OutputType.Transform != subStorageOutput.Type) - { - continue; - } - - Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; - foreach (Row row in instanceSummaryInformationTable.Rows) - { - if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) - { - row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); - break; - } - } - } -#endif - } - else if ("ProductLanguage" == propertySymbol.Id.Id) - { - languageSymbol = propertySymbol; - } - - // Add the property name and value to the variableCache. - if (variableCache != null) - { - variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value; - } - } - - if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value)) - { - if (languageSymbol == null) - { - languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage"))); - } - - this.PackageSymbol.Language = this.FallbackLcid.ToString(); - languageSymbol.Value = this.FallbackLcid.ToString(); - } - } - - this.DelayedVariablesCache = variableCache; - this.ProductLanguage = languageSymbol?.Value; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs deleted file mode 100644 index 039ba495..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Defines the file transfers necessary to layout the uncompressed files. - /// - internal class ProcessUncompressedFilesCommand - { - public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) - { - this.Section = section; - this.BackendHelper = backendHelper; - this.PathResolver = pathResolver; - } - - private IntermediateSection Section { get; } - - public IBackendHelper BackendHelper { get; } - - public IPathResolver PathResolver { get; } - - public string DatabasePath { private get; set; } - - public IEnumerable FileFacades { 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 IEnumerable FileTransfers { get; private set; } - - public IEnumerable TrackedFiles { get; private set; } - - public void Execute() - { - var fileTransfers = new List(); - - var trackedFiles = new List(); - - var directories = new Dictionary(); - - var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); - - using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) - { - using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) - { - foreach (var directoryRecord in directoryView.Records) - { - var sourceName = this.BackendHelper.GetMsiFileName(directoryRecord.GetString(3), true, this.LongNamesInImage); - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); - - directories.Add(directoryRecord.GetString(1), resolvedDirectory); - } - } - - using (var fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) - { - using (var fileQueryRecord = new Record(1)) - { - // for each file in the array of uncompressed files - foreach (var facade in this.FileFacades) - { - var mediaSymbol = mediaRows[facade.DiskId]; - string relativeFileLayoutPath = null; - var mediaLayoutFolder = mediaSymbol.Layout; - - var mediaLayoutDirectory = this.ResolveMedia(mediaSymbol, mediaLayoutFolder, this.LayoutDirectory); - - // setup up the query record and find the appropriate file in the - // previously executed file view - fileQueryRecord[1] = facade.Id; - fileView.Execute(fileQueryRecord); - - using (var fileRecord = fileView.Fetch()) - { - if (null == fileRecord) - { - throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id)); - } - - relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); - } - - // finally put together the base media layout path and the relative file layout path - var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); - - var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber); - fileTransfers.Add(transfer); - - // Track the location where the cabinet will be placed. If the transfer is - // redundant then then the file should not be cleaned. This is important - // because if the source and destination of the transfer is the same, we - // don't want to clean the file because we'd be deleting the original - // (and that would be bad). - var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber); - tracked.Clean = !transfer.Redundant; - - trackedFiles.Add(tracked); - } - } - } - } - - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs deleted file mode 100644 index 94fa0a6a..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ /dev/null @@ -1,714 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - /// - /// Set sequence numbers for all the actions and create symbols in the output object. - /// - internal class SequenceActionsCommand - { - public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - - this.RelativeActionsForActions = new Dictionary(); - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private Dictionary RelativeActionsForActions { get; } - - public void Execute() - { - var requiredActionSymbols = new Dictionary(); - - // Index all the action symbols and look for collisions. - foreach (var actionSymbol in this.Section.Symbols.OfType()) - { - if (actionSymbol.Overridable) // overridable action - { - if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) - { - if (collidingActionSymbol.Overridable) - { - this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - if (null != collidingActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); - } - } - } - else - { - requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); - } - } - else // unsequenced or sequenced action. - { - // Unsequenced action (allowed for certain standard actions). - if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue) - { - if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction)) - { - // Populate the sequence from the standard action - actionSymbol.Sequence = standardAction.Sequence; - } - else // not a supported unscheduled action. - { - throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); - } - } - - if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) - { - this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - if (null != collidingActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers)); - } - } - else - { - requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; - } - } - } - - // Get the standard actions required based on symbols in the section. - var requiredStandardActions = this.GetRequiredStandardActions(); - - // Add the overridable action symbols that are not overridden to the required action symbols. - foreach (var actionSymbol in requiredStandardActions.Values) - { - if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) - { - requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); - } - } - - // Suppress the required actions that are overridable. - foreach (var suppressActionSymbol in this.Section.Symbols.OfType()) - { - var key = suppressActionSymbol.Id.Id; - - // If there is an overridable symbol to suppress; suppress it. There is no warning if there - // is no action to suppress because the action may be suppressed from a merge module in - // the binder. - if (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol)) - { - if (requiredActionSymbol.Overridable) - { - this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString())); - if (null != requiredActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers)); - } - - requiredActionSymbols.Remove(key); - } - else // suppressing a non-overridable action symbol - { - this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action)); - if (null != requiredActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers)); - } - } - } - } - - // A dictionary used for detecting cyclic references among action symbols. - var firstReference = new Dictionary(); - - // Build up dependency trees of the relatively scheduled actions. - // Use ToList() to create a copy of the required action symbols so that new symbols can - // be added while enumerating. - foreach (var actionSymbol in requiredActionSymbols.Values.ToList()) - { - if (!actionSymbol.Sequence.HasValue) - { - // check for standard actions that don't have a sequence number in a merge module - if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) - { - this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - - this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); - } - else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number - { - this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - } - - // Look for standard actions with sequence restrictions that aren't necessarily scheduled based - // on the presence of a particular table. - if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles")) - { - WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction); - requiredActionSymbols.Add(standardAction.Id.Id, standardAction); - } - - // Schedule actions. - List scheduledActionSymbols; - if (SectionType.Module == this.Section.Type) - { - scheduledActionSymbols = requiredActionSymbols.Values.ToList(); - } - else - { - scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols); - } - - // Remove all existing WixActionSymbols from the section then add the - // scheduled actions back to the section. - var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList(); - - foreach (var removeSymbol in removeActionSymbols) - { - this.Section.RemoveSymbol(removeSymbol); - } - - foreach (var action in scheduledActionSymbols) - { - this.Section.AddSymbol(action); - } - } - - private Dictionary GetRequiredStandardActions() - { - var overridableActionSymbols = new Dictionary(); - - var requiredActionIds = this.GetRequiredActionIds(); - - foreach (var actionId in requiredActionIds) - { - WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction); - overridableActionSymbols.Add(standardAction.Id.Id, standardAction); - } - - return overridableActionSymbols; - } - - private List ScheduleActions(Dictionary requiredActionSymbols) - { - var scheduledActionSymbols = new List(); - - // Process each sequence table individually. - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - // Create a collection of just the action symbols in this sequence - var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList(); - - // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers). - var absoluteActionSymbols = new List(); - foreach (var actionSymbol in sequenceActionSymbols) - { - if (actionSymbol.Sequence.HasValue) - { - // Look for sequence number collisions - foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols) - { - if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence) - { - this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0)); - if (null != sequenceScheduledActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers)); - } - } - } - - absoluteActionSymbols.Add(actionSymbol); - } - } - - absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0)); - - // Schedule the relatively scheduled actions (by resolving the dependency trees). - var previousUsedSequence = 0; - var relativeActionSymbols = new List(); - for (int j = 0; j < absoluteActionSymbols.Count; j++) - { - var absoluteActionSymbol = absoluteActionSymbols[j]; - - // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol. - var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol); - - // Check for relatively scheduled actions occuring before/after a special action - // (those actions with a negative sequence number). - if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any())) - { - // Create errors for all the before actions. - foreach (var actionSymbol in relativeActions.PreviousActions) - { - this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); - } - - // Create errors for all the after actions. - foreach (var actionSymbol in relativeActions.NextActions) - { - this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); - } - - // If there is source line information for the absolutely scheduled action display it - if (absoluteActionSymbol.SourceLineNumbers != null) - { - this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers)); - } - - continue; - } - - // Schedule the action symbols before this one. - var unusedSequence = absoluteActionSymbol.Sequence - 1; - for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--) - { - var relativeActionSymbol = relativeActions.PreviousActions[i]; - - // look for collisions - if (unusedSequence == previousUsedSequence) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); - if (absoluteActionSymbol.SourceLineNumbers != null) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); - } - - unusedSequence++; - } - - relativeActionSymbol.Sequence = unusedSequence; - relativeActionSymbols.Add(relativeActionSymbol); - - unusedSequence--; - } - - // Determine the next used action sequence number. - var nextUsedSequence = Int16.MaxValue + 1; - if (absoluteActionSymbols.Count > j + 1) - { - nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0; - } - - // Schedule the action symbols after this one. - unusedSequence = absoluteActionSymbol.Sequence + 1; - for (var i = 0; i < relativeActions.NextActions.Count; i++) - { - var relativeActionSymbol = relativeActions.NextActions[i]; - - if (unusedSequence == nextUsedSequence) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); - if (absoluteActionSymbol.SourceLineNumbers != null) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); - } - - unusedSequence--; - } - - relativeActionSymbol.Sequence = unusedSequence; - relativeActionSymbols.Add(relativeActionSymbol); - - unusedSequence++; - } - - // keep track of this sequence number as the previous used sequence number for the next iteration - previousUsedSequence = absoluteActionSymbol.Sequence ?? 0; - } - - // add the absolutely and relatively scheduled actions to the list of scheduled actions - scheduledActionSymbols.AddRange(absoluteActionSymbols); - scheduledActionSymbols.AddRange(relativeActionSymbols); - } - - return scheduledActionSymbols; - } - - private IEnumerable GetRequiredActionIds() - { - var set = new HashSet(); - - // gather the required actions for the output type - if (SectionType.Product == this.Section.Type) - { - // AdminExecuteSequence table - set.Add("AdminExecuteSequence/CostFinalize"); - set.Add("AdminExecuteSequence/CostInitialize"); - set.Add("AdminExecuteSequence/FileCost"); - set.Add("AdminExecuteSequence/InstallAdminPackage"); - set.Add("AdminExecuteSequence/InstallFiles"); - set.Add("AdminExecuteSequence/InstallFinalize"); - set.Add("AdminExecuteSequence/InstallInitialize"); - set.Add("AdminExecuteSequence/InstallValidate"); - - // AdminUISequence table - set.Add("AdminUISequence/CostFinalize"); - set.Add("AdminUISequence/CostInitialize"); - set.Add("AdminUISequence/ExecuteAction"); - set.Add("AdminUISequence/FileCost"); - - // AdvtExecuteSequence table - set.Add("AdvertiseExecuteSequence/CostFinalize"); - set.Add("AdvertiseExecuteSequence/CostInitialize"); - set.Add("AdvertiseExecuteSequence/InstallInitialize"); - set.Add("AdvertiseExecuteSequence/InstallFinalize"); - set.Add("AdvertiseExecuteSequence/InstallValidate"); - set.Add("AdvertiseExecuteSequence/PublishFeatures"); - set.Add("AdvertiseExecuteSequence/PublishProduct"); - - // InstallExecuteSequence table - set.Add("InstallExecuteSequence/CostFinalize"); - set.Add("InstallExecuteSequence/CostInitialize"); - set.Add("InstallExecuteSequence/FileCost"); - set.Add("InstallExecuteSequence/InstallFinalize"); - set.Add("InstallExecuteSequence/InstallInitialize"); - set.Add("InstallExecuteSequence/InstallValidate"); - set.Add("InstallExecuteSequence/ProcessComponents"); - set.Add("InstallExecuteSequence/PublishFeatures"); - set.Add("InstallExecuteSequence/PublishProduct"); - set.Add("InstallExecuteSequence/RegisterProduct"); - set.Add("InstallExecuteSequence/RegisterUser"); - set.Add("InstallExecuteSequence/UnpublishFeatures"); - set.Add("InstallExecuteSequence/ValidateProductID"); - - // InstallUISequence table - set.Add("InstallUISequence/CostFinalize"); - set.Add("InstallUISequence/CostInitialize"); - set.Add("InstallUISequence/ExecuteAction"); - set.Add("InstallUISequence/FileCost"); - set.Add("InstallUISequence/ValidateProductID"); - } - - // Gather the required actions for each symbol type. - foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct()) - { - switch (symbolType) - { - case SymbolDefinitionType.AppSearch: - set.Add("InstallExecuteSequence/AppSearch"); - set.Add("InstallUISequence/AppSearch"); - break; - case SymbolDefinitionType.CCPSearch: - set.Add("InstallExecuteSequence/AppSearch"); - set.Add("InstallExecuteSequence/CCPSearch"); - set.Add("InstallExecuteSequence/RMCCPSearch"); - set.Add("InstallUISequence/AppSearch"); - set.Add("InstallUISequence/CCPSearch"); - set.Add("InstallUISequence/RMCCPSearch"); - break; - case SymbolDefinitionType.Class: - set.Add("AdvertiseExecuteSequence/RegisterClassInfo"); - set.Add("InstallExecuteSequence/RegisterClassInfo"); - set.Add("InstallExecuteSequence/UnregisterClassInfo"); - break; - case SymbolDefinitionType.Complus: - set.Add("InstallExecuteSequence/RegisterComPlus"); - set.Add("InstallExecuteSequence/UnregisterComPlus"); - break; - case SymbolDefinitionType.Component: - case SymbolDefinitionType.CreateFolder: - set.Add("InstallExecuteSequence/CreateFolders"); - set.Add("InstallExecuteSequence/RemoveFolders"); - break; - case SymbolDefinitionType.DuplicateFile: - set.Add("InstallExecuteSequence/DuplicateFiles"); - set.Add("InstallExecuteSequence/RemoveDuplicateFiles"); - break; - case SymbolDefinitionType.Environment: - set.Add("InstallExecuteSequence/WriteEnvironmentStrings"); - set.Add("InstallExecuteSequence/RemoveEnvironmentStrings"); - break; - case SymbolDefinitionType.Extension: - set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo"); - set.Add("InstallExecuteSequence/RegisterExtensionInfo"); - set.Add("InstallExecuteSequence/UnregisterExtensionInfo"); - break; - case SymbolDefinitionType.File: - set.Add("InstallExecuteSequence/InstallFiles"); - set.Add("InstallExecuteSequence/RemoveFiles"); - - var foundFont = false; - var foundSelfReg = false; - var foundBindPath = false; - foreach (var file in this.Section.Symbols.OfType()) - { - if (!foundFont && !String.IsNullOrEmpty(file.FontTitle)) - { - set.Add("InstallExecuteSequence/RegisterFonts"); - set.Add("InstallExecuteSequence/UnregisterFonts"); - foundFont = true; - } - - if (!foundSelfReg && file.SelfRegCost.HasValue) - { - set.Add("InstallExecuteSequence/SelfRegModules"); - set.Add("InstallExecuteSequence/SelfUnregModules"); - foundSelfReg = true; - } - - if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath)) - { - set.Add("InstallExecuteSequence/BindImage"); - foundBindPath = true; - } - } - break; - case SymbolDefinitionType.IniFile: - set.Add("InstallExecuteSequence/WriteIniValues"); - set.Add("InstallExecuteSequence/RemoveIniValues"); - break; - case SymbolDefinitionType.IsolatedComponent: - set.Add("InstallExecuteSequence/IsolateComponents"); - break; - case SymbolDefinitionType.LaunchCondition: - set.Add("InstallExecuteSequence/LaunchConditions"); - set.Add("InstallUISequence/LaunchConditions"); - break; - case SymbolDefinitionType.MIME: - set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo"); - set.Add("InstallExecuteSequence/RegisterMIMEInfo"); - set.Add("InstallExecuteSequence/UnregisterMIMEInfo"); - break; - case SymbolDefinitionType.MoveFile: - set.Add("InstallExecuteSequence/MoveFiles"); - break; - case SymbolDefinitionType.Assembly: - set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies"); - set.Add("InstallExecuteSequence/MsiPublishAssemblies"); - set.Add("InstallExecuteSequence/MsiUnpublishAssemblies"); - break; - case SymbolDefinitionType.MsiServiceConfig: - case SymbolDefinitionType.MsiServiceConfigFailureActions: - set.Add("InstallExecuteSequence/MsiConfigureServices"); - break; - case SymbolDefinitionType.ODBCDataSource: - case SymbolDefinitionType.ODBCTranslator: - case SymbolDefinitionType.ODBCDriver: - set.Add("InstallExecuteSequence/SetODBCFolders"); - set.Add("InstallExecuteSequence/InstallODBC"); - set.Add("InstallExecuteSequence/RemoveODBC"); - break; - case SymbolDefinitionType.ProgId: - set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo"); - set.Add("InstallExecuteSequence/RegisterProgIdInfo"); - set.Add("InstallExecuteSequence/UnregisterProgIdInfo"); - break; - case SymbolDefinitionType.PublishComponent: - set.Add("AdvertiseExecuteSequence/PublishComponents"); - set.Add("InstallExecuteSequence/PublishComponents"); - set.Add("InstallExecuteSequence/UnpublishComponents"); - break; - case SymbolDefinitionType.Registry: - case SymbolDefinitionType.RemoveRegistry: - set.Add("InstallExecuteSequence/WriteRegistryValues"); - set.Add("InstallExecuteSequence/RemoveRegistryValues"); - break; - case SymbolDefinitionType.RemoveFile: - set.Add("InstallExecuteSequence/RemoveFiles"); - break; - case SymbolDefinitionType.ServiceControl: - set.Add("InstallExecuteSequence/StartServices"); - set.Add("InstallExecuteSequence/StopServices"); - set.Add("InstallExecuteSequence/DeleteServices"); - break; - case SymbolDefinitionType.ServiceInstall: - set.Add("InstallExecuteSequence/InstallServices"); - break; - case SymbolDefinitionType.Shortcut: - set.Add("AdvertiseExecuteSequence/CreateShortcuts"); - set.Add("InstallExecuteSequence/CreateShortcuts"); - set.Add("InstallExecuteSequence/RemoveShortcuts"); - break; - case SymbolDefinitionType.TypeLib: - set.Add("InstallExecuteSequence/RegisterTypeLibraries"); - set.Add("InstallExecuteSequence/UnregisterTypeLibraries"); - break; - case SymbolDefinitionType.Upgrade: - set.Add("InstallExecuteSequence/FindRelatedProducts"); - set.Add("InstallUISequence/FindRelatedProducts"); - - // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on - // at least one UpgradeVersion element. - if (this.Section.Symbols.OfType().Any(t => t.MigrateFeatures)) - { - set.Add("InstallExecuteSequence/MigrateFeatureStates"); - set.Add("InstallUISequence/MigrateFeatureStates"); - } - break; - } - } - - return set; - } - - /// - /// Sequence an action before or after a standard action. - /// - /// The action symbol to be sequenced. - /// Collection of actions which must be included. - /// A dictionary used for detecting cyclic references among action symbols. - private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) - { - var after = false; - - if (actionSymbol.After != null) - { - after = true; - } - else if (actionSymbol.Before == null) - { - throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); - } - - var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); - var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName; - - if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) - { - // If the missing parent action is a standard action (with a suggested sequence number), add it. - if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol)) - { - // Create a clone to avoid modifying the static copy of the object. - // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone(); - - requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol); - } - else - { - throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this."); - } - } - - this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); - - // Add this action to the appropriate list of dependent action symbols. - var relativeActions = this.GetRelativeActions(parentActionSymbol); - var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions); - relatedSymbols.Add(actionSymbol); - } - - /// - /// Check the specified action symbol to see if it leads to a cycle. - /// - /// Use the provided dictionary to note the initial action symbol that first led to each action - /// symbol. Any action symbol encountered that has already been encountered starting from a different - /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is - /// also not part of a cycle. However, any action symbol encountered that has already been encountered - /// starting from the same initial action symbol is an indication that the current action symbol is - /// part of a cycle. - /// - /// The action symbol to be checked. - /// Collection of actions which must be included. - /// The first encountered action symbol that led to each action symbol. - private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) - { - WixActionSymbol currentActionSymbol = null; - var parentActionSymbol = actionSymbol; - - do - { - var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; - currentActionSymbol = parentActionSymbol; - - if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) - { - firstReference[currentActionSymbol] = actionSymbol; - } - else if (existingInitialActionSymbol == actionSymbol) - { - this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); - } - - parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); - } while (null != parentActionSymbol && !this.Messaging.EncounteredError); - } - - /// - /// Get the action symbol that is the parent of the given action symbol. - /// - /// The given action symbol. - /// Collection of actions which must be included. - /// Null if there is no parent. Used for loop termination. - private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) - { - if (null == actionSymbol.Before && null == actionSymbol.After) - { - return null; - } - - var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); - - if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) - { - WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); - } - - return parentActionSymbol; - } - - - private RelativeActions GetRelativeActions(WixActionSymbol action) - { - if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) - { - relativeActions = new RelativeActions(); - this.RelativeActionsForActions.Add(action.Id.Id, relativeActions); - } - - return relativeActions; - } - - private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action) - { - var relativeActions = new RelativeActions(); - - if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) - { - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions); - - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions); - } - - return relativeActions; - } - - private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List actions, List visitedActions) - { - foreach (var action in actions.Where(a => a.SequenceTable == sequenceType)) - { - if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) - { - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions); - } - - visitedActions.Add(action); - - if (actionRelatives != null) - { - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions); - } - } - } - - private class RelativeActions - { - public List PreviousActions { get; } = new List(); - - public List NextActions { get; } = new List(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs deleted file mode 100644 index 0f77abfc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Update file information. - /// - internal class UpdateFileFacadesCommand - { - public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable fileFacades, IEnumerable updateFileFacades, IDictionary variableCache, bool overwriteHash) - { - this.Messaging = messaging; - this.Section = section; - this.FileFacades = fileFacades; - this.UpdateFileFacades = updateFileFacades; - this.VariableCache = variableCache; - this.OverwriteHash = overwriteHash; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IEnumerable FileFacades { get; } - - private IEnumerable UpdateFileFacades { get; } - - private bool OverwriteHash { get; } - - private IDictionary VariableCache { get; } - - public void Execute() - { - var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) - { - this.UpdateFileFacade(file, assemblyNameSymbols); - } - } - - private void UpdateFileFacade(IFileFacade facade, Dictionary assemblyNameSymbols) - { - FileInfo fileInfo = null; - try - { - fileInfo = new FileInfo(facade.SourcePath); - } - catch (ArgumentException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); - return; - } - catch (PathTooLongException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); - return; - } - catch (NotSupportedException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); - return; - } - - if (!fileInfo.Exists) - { - this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); - return; - } - - using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - if (Int32.MaxValue < fileStream.Length) - { - throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); - } - - facade.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(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); - } - else - { - throw new WixException(ErrorMessages.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 != facade.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 => facade.Version.Equals(r.Id, StringComparison.Ordinal))) - { - this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); - } - } - else - { - if (null != facade.Language) - { - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); - } - - int[] hash; - try - { - Installer.GetFileHash(fileInfo.FullName, 0, out hash); - } - catch (Win32Exception e) - { - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND - { - throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); - } - else - { - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); - } - } - - if (null == facade.Hash) - { - facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); - } - - facade.Hash.Options = 0; - facade.Hash.HashPart1 = hash[0]; - facade.Hash.HashPart2 = hash[1]; - facade.Hash.HashPart3 = hash[2]; - facade.Hash.HashPart4 = hash[3]; - } - } - 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(facade.Version)) - { - facade.Version = version; - } - else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. - { - // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching - // the version value). We didn't find it so, we will override the default version they provided with the actual - // 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. - facade.Version = version; - } - - if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) - { - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); - } - else // override the default provided by the user (usually nothing) with the actual language from the file itself. - { - facade.Language = language; - } - - // Populate the binder variables for this file information if requested. - if (null != this.VariableCache) - { - if (!String.IsNullOrEmpty(facade.Version)) - { - var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); - this.VariableCache[key] = facade.Version; - } - - if (!String.IsNullOrEmpty(facade.Language)) - { - var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); - this.VariableCache[key] = facade.Language; - } - } - } - - // If this is a CLR assembly, load the assembly and get the assembly name information - if (AssemblyType.DotNetAssembly == facade.AssemblyType) - { - try - { - var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); - - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); - - if (!String.IsNullOrEmpty(assemblyName.Architecture)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); - } - // TODO: WiX v3 seemed to do this but not clear it should actually be done. - //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) - //{ - // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); - //} - - if (assemblyName.StrongNamedSigned) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); - } - else if (facade.AssemblyApplicationFileRef == null) - { - throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); - } - - if (!String.IsNullOrEmpty(assemblyName.FileVersion)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); - } - - // add the assembly name to the information cache - if (null != this.VariableCache) - { - this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - else if (AssemblyType.Win32Assembly == facade.AssemblyType) - { - // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through - // all files like this. Even though this is a rare case it looks like we might be able to index the - // file earlier. - var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); - if (null == fileManifest) - { - this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); - } - - try - { - var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); - - if (!String.IsNullOrEmpty(assemblyName.Name)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); - } - - if (!String.IsNullOrEmpty(assemblyName.Version)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); - } - - if (!String.IsNullOrEmpty(assemblyName.Type)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); - } - - if (!String.IsNullOrEmpty(assemblyName.Architecture)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); - } - - if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - } - - /// - /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise - /// create a new row. - /// - /// MsiAssemblyName table. - /// FileFacade containing the assembly read for the MsiAssemblyName row. - /// MsiAssemblyName name. - /// MsiAssemblyName value. - private void SetMsiAssemblyName(Dictionary assemblyNameSymbols, IFileFacade facade, string name, string value) - { - // check for null value (this can occur when grabbing the file version from an assembly without one) - if (String.IsNullOrEmpty(value)) - { - this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); - } - else - { - // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. - if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && - String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && - !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); - } - - // override directly authored value - var lookup = String.Concat(facade.ComponentRef, "/", name); - if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) - { - assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) - { - ComponentRef = facade.ComponentRef, - Name = name, - Value = value, - }); - - if (null == facade.AssemblyNames) - { - facade.AssemblyNames = new List(); - } - - facade.AssemblyNames.Add(assemblyNameSymbol); - - assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); - } - - assemblyNameSymbol.Value = value; - - if (this.VariableCache != null) - { - var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); - this.VariableCache[key] = value; - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs deleted file mode 100644 index 66a648cc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class UpdateFromTextFilesCommand - { - public UpdateFromTextFilesCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public void Execute() - { - foreach (var bbControl in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) - { - bbControl.Text = this.ReadTextFile(bbControl.SourceLineNumbers, bbControl.SourceFile.Path); - } - - foreach (var control in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) - { - control.Text = this.ReadTextFile(control.SourceLineNumbers, control.SourceFile.Path); - } - - foreach (var customAction in this.Section.Symbols.OfType().Where(c => c.ScriptFile != null)) - { - customAction.Target = this.ReadTextFile(customAction.SourceLineNumbers, customAction.ScriptFile.Path); - } - } - - /// - /// 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) - { - try - { - using (var reader = new StreamReader(source)) - { - return reader.ReadToEnd(); - } - } - catch (DirectoryNotFoundException e) - { - this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (FileNotFoundException e) - { - this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (IOException e) - { - this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (NotSupportedException) - { - this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, source)); - } - - return null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs deleted file mode 100644 index affec09f..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - - internal class UpdateMediaSequencesCommand - { - public UpdateMediaSequencesCommand(IntermediateSection section, IEnumerable fileFacades) - { - this.Section = section; - this.FileFacades = fileFacades; - } - - private IntermediateSection Section { get; } - - private IEnumerable FileFacades { get; } - - public void Execute() - { - var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); - - // Calculate sequence numbers and media disk id layout for all file media information objects. - if (SectionType.Module == this.Section.Type) - { - var lastSequence = 0; - - foreach (var facade in this.FileFacades) - { - facade.Sequence = ++lastSequence; - } - } - else - { - var lastSequence = 0; - MediaSymbol mediaSymbol = null; - var patchGroups = new Dictionary>(); - - // Sequence the non-patch-added files. - foreach (var facade in this.FileFacades) - { - if (null == mediaSymbol) - { - mediaSymbol = mediaRows[facade.DiskId]; - if (SectionType.Patch == this.Section.Type) - { - // patch Media cannot start at zero - lastSequence = mediaSymbol.LastSequence ?? 1; - } - } - else if (mediaSymbol.DiskId != facade.DiskId) - { - mediaSymbol.LastSequence = lastSequence; - mediaSymbol = mediaRows[facade.DiskId]; - } - - if (facade.PatchGroup.HasValue) - { - if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup)) - { - patchGroup = new List(); - patchGroups.Add(facade.PatchGroup.Value, patchGroup); - } - - patchGroup.Add(facade); - } - else if (!facade.FromModule) - { - facade.Sequence = ++lastSequence; - } - } - - if (null != mediaSymbol) - { - mediaSymbol.LastSequence = lastSequence; - mediaSymbol = null; - } - - // Sequence the patch-added files. - foreach (var patchGroup in patchGroups.Values) - { - foreach (var facade in patchGroup) - { - if (null == mediaSymbol) - { - mediaSymbol = mediaRows[facade.DiskId]; - } - else if (mediaSymbol.DiskId != facade.DiskId) - { - mediaSymbol.LastSequence = lastSequence; - mediaSymbol = mediaRows[facade.DiskId]; - } - - facade.Sequence = ++lastSequence; - } - } - - if (null != mediaSymbol) - { - mediaSymbol.LastSequence = lastSequence; - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs deleted file mode 100644 index 981fa0a4..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs +++ /dev/null @@ -1,451 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class UpdateTransformsWithFileFacades - { - public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) - { - this.Messaging = messaging; - this.Output = output; - this.SubStorages = subStorages; - this.TableDefinitions = tableDefinitions; - this.FileFacades = fileFacades; - } - - private IMessaging Messaging { get; } - - private WindowsInstallerData Output { get; } - - private IEnumerable SubStorages { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private IEnumerable FileFacades { get; } - - public void Execute() - { - var fileFacadesByDiskId = new Dictionary>(); - - // Index patch file facades by diskId+fileId. - foreach (var facade in this.FileFacades) - { - if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) - { - mediaFacades = new Dictionary(); - fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); - } - - mediaFacades.Add(facade.Id, facade); - } - - var patchMediaRows = new RowDictionary(this.Output.Tables["Media"]); - - // Index paired transforms by name without the "#" prefix. - var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); - - // Copy File bind data into substorages - foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) - { - var mainTransform = substorage.Data; - - var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); - - var pairedTransform = pairedTransforms["#" + substorage.Name]; - - // Copy Media.LastSequence. - var pairedMediaTable = pairedTransform.Tables["Media"]; - foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) - { - var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); - pairedMediaRow.LastSequence = patchMediaRow.LastSequence; - } - - // Validate file row changes for keypath-related issues - this.ValidateFileRowChanges(mainTransform); - - // Index File table of pairedTransform - var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - - var mainFileTable = mainTransform.Tables["File"]; - if (null != mainFileTable) - { - // 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) - { - continue; - } - - // Index patch files by diskId+fileId - if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) - { - mediaFacades = new Dictionary(); - fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); - } - - // copy data from the patch back to the transform - if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) - { - var patchFileRow = facade.GetFileRow(); - var pairedFileRow = pairedFileRows.Get(mainFileRow.File); - - for (var i = 0; i < patchFileRow.Fields.Length; i++) - { - var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; - var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; - - if (1 == i) - { - // File.Component_ changes should not come from the shared file rows - // that contain the file information as each individual transform might - // have different changes (or no changes at all). - } - else if (6 == i) // File.Attributes should not changed for binary deltas - { -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - // File.Attribute should not change for binary deltas - pairedFileRow.Attributes = mainFileRow.Attributes; - mainFileRow.Fields[i].Modified = false; - } -#endif - } - else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform - { - // file sequence is updated in Patch table instead of File table for delta patches -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - pairedFileRow.Fields[i].Modified = false; - } - else -#endif - { - pairedFileRow[i] = patchFileRow[i]; - pairedFileRow.Fields[i].Modified = true; - } - mainFileRow.Fields[i].Modified = false; - } - else if (patchValue != mainValue) - { - mainFileRow[i] = patchFileRow[i]; - mainFileRow.Fields[i].Modified = true; - if (mainFileRow.Operation == RowOperation.None) - { - mainFileRow.Operation = RowOperation.Modify; - } - } - } - - // Copy MsiFileHash row for this File. - if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) - { - //patchHashRow = patchFileRow.Hash; - throw new NotImplementedException(); - } - - if (null != patchHashRow) - { - var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); - var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); - for (var i = 0; i < patchHashRow.Fields.Length; i++) - { - mainHashRow[i] = patchHashRow[i]; - if (i > 1) - { - // assume all hash fields have been modified - mainHashRow.Fields[i].Modified = true; - } - } - - // assume the MsiFileHash operation follows the File one - mainHashRow.Operation = mainFileRow.Operation; - } - - // copy MsiAssemblyName rows for this File -#if TODO_PATCHING - List patchAssemblyNameRows = patchFileRow.AssemblyNames; - if (null != patchAssemblyNameRows) - { - var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - foreach (var patchAssemblyNameRow in patchAssemblyNameRows) - { - // Copy if there isn't an identical modified/added row already in the transform. - var foundMatchingModifiedRow = false; - foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) - { - if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) - { - foundMatchingModifiedRow = true; - break; - } - } - - if (!foundMatchingModifiedRow) - { - var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); - for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) - { - mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; - } - - // assume value field has been modified - mainAssemblyNameRow.Fields[2].Modified = true; - mainAssemblyNameRow.Operation = mainFileRow.Operation; - } - } - } -#endif - - // Add patch header for this file -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. - this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); - this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); - - // Add to Patch table - var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); - if (0 == patchTable.Rows.Count) - { - patchTable.Operation = TableOperation.Add; - } - - var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); - patchRow[0] = patchFileRow.File; - patchRow[1] = patchFileRow.Sequence; - - var patchFile = new FileInfo(patchFileRow.Source); - patchRow[2] = (int)patchFile.Length; - patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; - - var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; - if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) - { - streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); - - var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); - if (0 == patchHeadersTable.Rows.Count) - { - patchHeadersTable.Operation = TableOperation.Add; - } - - var 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; - } -#endif - } - else - { - // TODO: throw because all transform rows should have made it into the patch - } - } - } - - this.Output.Tables.Remove("Media"); - this.Output.Tables.Remove("File"); - this.Output.Tables.Remove("MsiFileHash"); - this.Output.Tables.Remove("MsiAssemblyName"); - } - } - - /// - /// 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, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) - { - var tableName = table.ToString(); - - // Find/add PatchFiles action (also determine sequence for it). - // Search mainTransform first, then pairedTransform (pairedTransform overrides). - var hasPatchFilesAction = false; - var installFilesSequence = 0; - var duplicateFilesSequence = 0; - - TestSequenceTableForPatchFilesAction( - mainTransform.Tables[tableName], - ref hasPatchFilesAction, - ref installFilesSequence, - ref duplicateFilesSequence); - TestSequenceTableForPatchFilesAction( - pairedTransform.Tables[tableName], - ref hasPatchFilesAction, - ref installFilesSequence, - ref duplicateFilesSequence); - if (!hasPatchFilesAction) - { - WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionSymbol); - - var sequence = patchFilesActionSymbol.Sequence; - - // Test for default sequence value's appropriateness - if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) - { - if (0 != duplicateFilesSequence) - { - if (duplicateFilesSequence < installFilesSequence) - { - throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); - } - else - { - sequence = (duplicateFilesSequence + installFilesSequence) / 2; - if (installFilesSequence == sequence || duplicateFilesSequence == sequence) - { - throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); - } - } - } - else - { - sequence = installFilesSequence + 1; - } - } - - var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); - if (0 == sequenceTable.Rows.Count) - { - sequenceTable.Operation = TableOperation.Add; - } - - var patchAction = sequenceTable.CreateRow(null); - patchAction[0] = patchFilesActionSymbol.Action; - patchAction[1] = patchFilesActionSymbol.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 sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) - { - if (null != sequenceTable) - { - foreach (var row in sequenceTable.Rows) - { - var actionName = row.FieldAsString(0); - switch (actionName) - { - case "PatchFiles": - hasPatchFilesAction = true; - break; - - case "InstallFiles": - installFilesSequence = row.FieldAsInteger(2); - break; - - case "DuplicateFiles": - duplicateFilesSequence = row.FieldAsInteger(2); - break; - } - } - } - } - - /// - /// 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(WindowsInstallerData transform) - { - var componentTable = transform.Tables["Component"]; - var 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; - } - - var componentKeyPath = new Dictionary(componentTable.Rows.Count); - - // Index the Component table for non-directory & non-registry key paths. - foreach (var row in componentTable.Rows) - { - var keyPath = row.FieldAsString(5); - if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) - { - componentKeyPath.Add(row.FieldAsString(0), keyPath); - } - } - - var componentWithChangedKeyPath = new Dictionary(); - var componentWithNonKeyPathChanged = new Dictionary(); - // Verify changes in the file table, now that file diffing has occurred - foreach (FileRow row in fileTable.Rows) - { - if (RowOperation.Modify != row.Operation) - { - continue; - } - - var fileId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); - - // 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 (var componentFile in componentWithNonKeyPathChanged) - { - // Make sure all changes to non keypath files also had a change in the keypath. - if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) - { - this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs deleted file mode 100644 index cf1e21c2..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Bind -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback - { - // Set of ICEs that have equivalent-or-better checks in WiX. - private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; - - public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Data = data; - this.OutputPath = outputPath; - this.CubeFiles = cubeFiles; - this.Ices = ices; - this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); - - this.IntermediateFolder = intermediateFolder; - this.OutputSourceLineNumber = new SourceLineNumber(outputPath); - } - - public IEnumerable TrackedFiles { get; private set; } - - /// - /// Encountered error implementation for . - /// - public bool EncounteredError => this.Messaging.EncounteredError; - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private WindowsInstallerData Data { get; } - - private string OutputPath { get; } - - private IEnumerable CubeFiles { get; } - - private IEnumerable Ices { get; } - - private IEnumerable SuppressedIces { get; } - - private string IntermediateFolder { get; } - - /// - /// Fallback when an exact source line number cannot be calculated for a validation error. - /// - private SourceLineNumber OutputSourceLineNumber { get; set; } - - private Dictionary SourceLineNumbersByTablePrimaryKey { get; set; } - - public void Execute() - { - var trackedFiles = new List(); - var stopwatch = Stopwatch.StartNew(); - - this.Messaging.Write(VerboseMessages.ValidatingDatabase()); - - // Ensure the temporary files can be created the working folder. - var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); - Directory.CreateDirectory(workingFolder); - - // Copy the database to a temporary location so it can be manipulated. - // Ensure it is not read-only. - var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); - FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); - - var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary); - trackedFiles.Add(trackWorkingDatabase); - - var attributes = File.GetAttributes(workingDatabasePath); - File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); - - var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); - validator.Execute(); - - stopwatch.Stop(); - this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); - - - this.TrackedFiles = trackedFiles; - } - - private void LogValidationMessage(ValidationMessage message) - { - var messageSourceLineNumbers = this.OutputSourceLineNumber; - if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) - { - messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); - } - - switch (message.Type) - { - case ValidationMessageType.InternalFailure: - case ValidationMessageType.Error: - this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); - break; - case ValidationMessageType.Warning: - this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); - break; - case ValidationMessageType.Info: - this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); - break; - default: - throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); - } - } - - /// - /// Validation blocked by other installation operation for . - /// - public void ValidationBlocked() - { - this.Messaging.Write(VerboseMessages.ValidationSerialized()); - } - - /// - /// Validation message implementation for . - /// - public bool ValidationMessage(ValidationMessage message) - { - this.LogValidationMessage(message); - return true; - } - - /// - /// 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. - private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable primaryKeys) - { - // Source line information only exists if an output file was supplied - if (this.Data == null) - { - // Use the file name as the source line information. - return this.OutputSourceLineNumber; - } - - // Index the source line information if it hasn't been indexed already. - if (this.SourceLineNumbersByTablePrimaryKey == null) - { - this.SourceLineNumbersByTablePrimaryKey = new Dictionary(); - - // Index each real table - foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) - { - // Index each row that contain source line information - foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) - { - // Index the row using its table name and primary key - var primaryKey = row.GetPrimaryKey(';'); - - if (!String.IsNullOrEmpty(primaryKey)) - { - try - { - var key = String.Concat(table.Name, ":", primaryKey); - this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); - } - catch (ArgumentException) - { - this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); - } - } - } - } - } - - return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs deleted file mode 100644 index aeda4443..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Decompile -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class DecompileMsiOrMsmCommand - { - public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable backendExtensions) - { - this.Context = context; - this.Extensions = backendExtensions; - this.Messaging = context.ServiceProvider.GetService(); - } - - private IDecompileContext Context { get; } - - private IEnumerable Extensions { get; } - - private IMessaging Messaging { get; } - - public IDecompileResult Execute() - { - var result = this.Context.ServiceProvider.GetService(); - - try - { - using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly)) - { - // Delete the directory and its files to prevent cab extraction failure due to an existing file. - if (Directory.Exists(this.Context.ExtractFolder)) - { - Directory.Delete(this.Context.ExtractFolder, true); - } - - var backendHelper = this.Context.ServiceProvider.GetService(); - - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, this.Context.DecompilePath, this.Context.DecompileType, this.Context.ExtractFolder, this.Context.IntermediateFolder, this.Context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false); - var output = unbindCommand.Execute(); - var extractedFilePaths = new List(unbindCommand.ExportedFiles); - - var decompiler = new Decompiler(this.Messaging, backendHelper, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule); - result.Document = decompiler.Decompile(output); - - result.Platform = GetPlatformFromOutput(output); - - // extract the files from the cabinets - if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets) - { - var fileDirectory = String.IsNullOrEmpty(this.Context.CabinetExtractFolder) ? Path.Combine(this.Context.ExtractFolder, "File") : this.Context.CabinetExtractFolder; - - var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, fileDirectory, this.Context.IntermediateFolder, this.Context.TreatProductAsModule); - extractCommand.Execute(); - - extractedFilePaths.AddRange(extractCommand.ExtractedFiles); - result.ExtractedFilePaths = extractedFilePaths; - } - else - { - result.ExtractedFilePaths = new string[0]; - } - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath)); - } - - throw; - } - - return result; - } - - private static Platform? GetPlatformFromOutput(WindowsInstallerData output) - { - var template = output.Tables["_SummaryInformation"]?.Rows.SingleOrDefault(row => row.FieldAsInteger(0) == 7)?.FieldAsString(1); - - return Decompiler.GetPlatformFromTemplateSummaryInformation(template?.Split(';')); - - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs deleted file mode 100644 index 0b45a8b3..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ /dev/null @@ -1,7596 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Decompile -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Decompiles an msi database into WiX source. - /// - internal class Decompiler - { - private static readonly Regex NullSplitter = new Regex(@"\[~]"); - - // NameToBit arrays - private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; - private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; - private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; - private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; - private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; - private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; - private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; - private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; - private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; - private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; - private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; - private XElement uiElement; - - /// - /// Creates a new decompiler object with a default set of table definitions. - /// - public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Extensions = extensions; - this.BaseSourcePath = baseSourcePath ?? "SourceDir"; - this.SuppressCustomTables = suppressCustomTables; - this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; - this.SuppressUI = suppressUI; - this.TreatProductAsModule = treatProductAsModule; - - this.ExtensionsByTableName = new Dictionary(); - this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); - - this.TableDefinitions = new TableDefinitionCollection(); - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IEnumerable Extensions { get; } - - private Dictionary ExtensionsByTableName { get; } - - private string BaseSourcePath { get; } - - private bool SuppressCustomTables { get; } - - private bool SuppressDroppingEmptyTables { get; } - - private bool SuppressRelativeActionSequencing { get; } - - private bool SuppressUI { get; } - - private bool TreatProductAsModule { get; } - - private OutputType OutputType { get; set; } - - private Dictionary StandardActions { get; } - - private bool Compressed { get; set; } - - private XElement RootElement { get; set; } - - private TableDefinitionCollection TableDefinitions { get; } - - private bool ShortNames { get; set; } - - private string ModularizationGuid { get; set; } - - private XElement UIElement - { - get - { - if (null == this.uiElement) - { - this.uiElement = new XElement(Names.UIElement); - this.RootElement.Add(this.uiElement); - } - - return this.uiElement; - } - } - - private Dictionary Singletons { get; } = new Dictionary(); - - private Dictionary IndexedElements { get; } = new Dictionary(); - - private Dictionary PatchTargetFiles { get; } = new Dictionary(); - - /// - /// Decompile the database file. - /// - /// The output to decompile. - /// The serialized WiX source code. - public XDocument Decompile(WindowsInstallerData output) - { - if (null == output) - { - throw new ArgumentNullException(nameof(output)); - } - - this.OutputType = output.Type; - - // collect the table definitions from the output - this.TableDefinitions.Clear(); - foreach (var table in output.Tables) - { - this.TableDefinitions.Add(table.Definition); - } - - // add any missing standard and wix-specific table definitions - foreach (var tableDefinition in WindowsInstallerTableDefinitions.All) - { - if (!this.TableDefinitions.Contains(tableDefinition.Name)) - { - this.TableDefinitions.Add(tableDefinition); - } - } - - // add any missing extension table definitions -#if TODO_DECOMPILER_EXTENSIONS - foreach (var extension in this.Extensions) - { - this.AddExtension(extension); - } -#endif - - switch (this.OutputType) - { - case OutputType.Module: - this.RootElement = new XElement(Names.ModuleElement); - break; - case OutputType.PatchCreation: - this.RootElement = new XElement(Names.PatchCreationElement); - break; - case OutputType.Product: - this.RootElement = new XElement(Names.PackageElement); - break; - default: - throw new InvalidOperationException("Unknown output type."); - } - - var xWix = new XElement(Names.WixElement, this.RootElement); - - // try to decompile the database file - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - this.InitializeDecompile(output.Tables, output.Codepage); - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // decompile the tables - this.DecompileTables(output); - - // finalize the decompiler and its extensions - this.FinalizeDecompile(output.Tables); - - // return the XML document only if decompilation completed successfully - var document = new XDocument(xWix); - return this.Messaging.EncounteredError ? null : document; - } - -#if TODO_DECOMPILER_EXTENSIONS - private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension) - { - if (null != extension.TableDefinitions) - { - foreach (TableDefinition tableDefinition in extension.TableDefinitions) - { - if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name)) - { - this.ExtensionsByTableName.Add(tableDefinition.Name, extension); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); - } - } - } - } -#endif - - internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template) - { - if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length) - { - switch (template[0]) - { - case "Intel": - return Platform.X86; - case "x64": - return Platform.X64; - case "Arm64": - return Platform.ARM64; - } - } - - return null; - } - - /// - /// Gets the element corresponding to the row it came from. - /// - /// The row corresponding to the element. - /// The indexed element. - private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - - /// - /// Gets the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The primary key corresponding to the element. - /// The indexed element. - private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; - - /// - /// Tries to get the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The indexed element. - /// Whether the element was found. - private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - - /// - /// Tries to get the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The indexed element. - /// The primary key corresponding to the element. - /// Whether the element was found. - private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement); - - /// - /// Index an element by its corresponding row. - /// - /// The row corresponding to the element. - /// The element to index. - private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element) - { - this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); - } - - /// - /// Index an element by its corresponding row. - /// - /// The element to index. - /// - /// - private void IndexElement(XElement element, string table, params string[] primaryKey) - { - this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element); - } - - private Dictionary> IndexTableOneToMany(IEnumerable rows, int column = 0) - { - return rows - .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row)) - .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); - } - - private Dictionary> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty(), column); - - private Dictionary> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty(), column); - - private void AddChildToParent(string parentName, XElement xChild, Row row, int column) - { - var key = row.FieldAsString(column); - if (this.TryGetIndexedElement(parentName, out var xParent, key)) - { - xParent.Add(xChild); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName)); - } - } - - private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column)); - - private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value) - { - if (!String.IsNullOrEmpty(value)) - { - xElement.SetAttributeValue(attributeName, value); - } - } - - private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value) - { - if (value.HasValue) - { - xElement.SetAttributeValue(attributeName, value); - } - } - - /// - /// Convert an Int32 into a DateTime. - /// - /// The Int32 value. - /// The DateTime. - private static DateTime ConvertIntegerToDateTime(int value) - { - var date = value / 65536; - var time = value % 65536; - - return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); - } - - /// - /// Set the common control attributes in a control element. - /// - /// The control attributes. - /// The control element. - private static void SetControlAttributes(int attributes, XElement xControl) - { - if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled)) - { - xControl.SetAttributeValue("Disabled", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect)) - { - xControl.SetAttributeValue("Indirect", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger)) - { - xControl.SetAttributeValue("Integer", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll)) - { - xControl.SetAttributeValue("LeftScroll", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned)) - { - xControl.SetAttributeValue("RightAligned", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO)) - { - xControl.SetAttributeValue("RightToLeft", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken)) - { - xControl.SetAttributeValue("Sunken", "yes"); - } - - if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible)) - { - xControl.SetAttributeValue("Hidden", "yes"); - } - } - - /// - /// Creates an action element. - /// - /// The action from which the element should be created. - private void CreateActionElement(WixActionSymbol actionSymbol) - { - XElement xAction; - - if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action - { - xAction = new XElement(Names.CustomElement, - new XAttribute("Action", actionSymbol.Action), - String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition)); - - switch (actionSymbol.Sequence) - { - case (-4): - xAction.SetAttributeValue("OnExit", "suspend"); - break; - case (-3): - xAction.SetAttributeValue("OnExit", "error"); - break; - case (-2): - xAction.SetAttributeValue("OnExit", "cancel"); - break; - case (-1): - xAction.SetAttributeValue("OnExit", "success"); - break; - default: - if (null != actionSymbol.Before) - { - xAction.SetAttributeValue("Before", actionSymbol.Before); - } - else if (null != actionSymbol.After) - { - xAction.SetAttributeValue("After", actionSymbol.After); - } - else if (actionSymbol.Sequence.HasValue) - { - xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); - } - break; - } - } - else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog - { - xAction = new XElement(Names.CustomElement, - new XAttribute("Dialog", actionSymbol.Action), - new XAttribute("Condition", actionSymbol.Condition)); - - switch (actionSymbol.Sequence) - { - case (-4): - xAction.SetAttributeValue("OnExit", "suspend"); - break; - case (-3): - xAction.SetAttributeValue("OnExit", "error"); - break; - case (-2): - xAction.SetAttributeValue("OnExit", "cancel"); - break; - case (-1): - xAction.SetAttributeValue("OnExit", "success"); - break; - default: - SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); - SetAttributeIfNotNull(xAction, "After", actionSymbol.After); - SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); - break; - } - } - else // possibly a standard action without suggested sequence information - { - xAction = this.CreateStandardActionElement(actionSymbol); - } - - // add the action element to the appropriate sequence element - if (null != xAction) - { - var sequenceTable = actionSymbol.SequenceTable.ToString(); - if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence)) - { - xSequence = new XElement(Names.WxsNamespace + sequenceTable); - - this.RootElement.Add(xSequence); - this.Singletons.Add(sequenceTable, xSequence); - } - - try - { - xSequence.Add(xAction); - } - catch (ArgumentException) // action/dialog is not valid for this sequence - { - this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - } - } - - /// - /// Creates a standard action element. - /// - /// The action row from which the element should be created. - /// The created element. - private XElement CreateStandardActionElement(WixActionSymbol actionSymbol) - { - XElement xStandardAction = null; - - switch (actionSymbol.Action) - { - case "AllocateRegistrySpace": - case "BindImage": - case "CostFinalize": - case "CostInitialize": - case "CreateFolders": - case "CreateShortcuts": - case "DeleteServices": - case "DuplicateFiles": - case "ExecuteAction": - case "FileCost": - case "InstallAdminPackage": - case "InstallFiles": - case "InstallFinalize": - case "InstallInitialize": - case "InstallODBC": - case "InstallServices": - case "InstallValidate": - case "IsolateComponents": - case "MigrateFeatureStates": - case "MoveFiles": - case "MsiPublishAssemblies": - case "MsiUnpublishAssemblies": - case "PatchFiles": - case "ProcessComponents": - case "PublishComponents": - case "PublishFeatures": - case "PublishProduct": - case "RegisterClassInfo": - case "RegisterComPlus": - case "RegisterExtensionInfo": - case "RegisterFonts": - case "RegisterMIMEInfo": - case "RegisterProduct": - case "RegisterProgIdInfo": - case "RegisterTypeLibraries": - case "RegisterUser": - case "RemoveDuplicateFiles": - case "RemoveEnvironmentStrings": - case "RemoveFiles": - case "RemoveFolders": - case "RemoveIniValues": - case "RemoveODBC": - case "RemoveRegistryValues": - case "RemoveShortcuts": - case "SelfRegModules": - case "SelfUnregModules": - case "SetODBCFolders": - case "StartServices": - case "StopServices": - case "UnpublishComponents": - case "UnpublishFeatures": - case "UnregisterClassInfo": - case "UnregisterComPlus": - case "UnregisterExtensionInfo": - case "UnregisterFonts": - case "UnregisterMIMEInfo": - case "UnregisterProgIdInfo": - case "UnregisterTypeLibraries": - case "ValidateProductID": - case "WriteEnvironmentStrings": - case "WriteIniValues": - case "WriteRegistryValues": - xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); - break; - - case "AppSearch": - this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); - - if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) - { - xStandardAction = new XElement(Names.AppSearchElement); - - SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition); - SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before); - SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After); - SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence); - - return xStandardAction; - } - break; - - case "CCPSearch": - case "DisableRollback": - case "FindRelatedProducts": - case "ForceReboot": - case "InstallExecute": - case "InstallExecuteAgain": - case "LaunchConditions": - case "RemoveExistingProducts": - case "ResolveSource": - case "RMCCPSearch": - case "ScheduleReboot": - xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); - Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction); - return xStandardAction; - - default: - this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - return null; - } - - if (xStandardAction != null) - { - this.SequenceStandardAction(actionSymbol, xStandardAction); - } - - return xStandardAction; - } - - /// - /// Applies the condition and sequence to a standard action element based on the action symbol data. - /// - /// Action data from the database. - /// Element to be sequenced. - private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction) - { - xAction.SetAttributeValue("Condition", actionSymbol.Condition); - - if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence) - { - this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - else if (actionSymbol.Sequence.HasValue) - { - xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); - } - } - - /// - /// Applies the condition and relative sequence to an action element based on the action row data. - /// - /// Action data from the database. - /// Element to be sequenced. - private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction) - { - SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition); - SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); - SetAttributeIfNotNull(xAction, "After", actionSymbol.After); - SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); - } - - /// - /// Ensure that a particular property exists in the decompiled output. - /// - /// The identifier of the property. - /// The property element. - private XElement EnsureProperty(string id) - { - XElement xProperty; - - if (!this.TryGetIndexedElement("Property", out xProperty, id)) - { - xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id)); - - this.RootElement.Add(xProperty); - this.IndexElement(xProperty, "Property", id); - } - - return xProperty; - } - - /// - /// Finalize decompilation. - /// - /// The collection of all tables. - private void FinalizeDecompile(TableIndexedCollection tables) - { - if (OutputType.PatchCreation == this.OutputType) - { - this.FinalizeFamilyFileRangesTable(tables); - } - else - { - this.FinalizeSummaryInformationStream(tables); - this.FinalizeCheckBoxTable(tables); - this.FinalizeComponentTable(tables); - this.FinalizeDialogTable(tables); - this.FinalizeDuplicateMoveFileTables(tables); - this.FinalizeFeatureComponentsTable(tables); - this.FinalizeFileTable(tables); - this.FinalizeMIMETable(tables); - this.FinalizeMsiLockPermissionsExTable(tables); - this.FinalizeLockPermissionsTable(tables); - this.FinalizeProgIdTable(tables); - this.FinalizePropertyTable(tables); - this.FinalizeRemoveFileTable(tables); - this.FinalizeSearchTables(tables); - this.FinalizeShortcutTable(tables); - this.FinalizeUpgradeTable(tables); - this.FinalizeSequenceTables(tables); - this.FinalizeVerbTable(tables); - } - } - - /// - /// Finalize the CheckBox table. - /// - /// The collection of all tables. - /// - /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with - /// a value in the Property column. This is then possibly matched up with a CheckBox row - /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. - /// - private void FinalizeCheckBoxTable(TableIndexedCollection tables) - { - // if the user has requested to suppress the UI elements, we have nothing to do - if (this.SuppressUI) - { - return; - } - - var checkBoxTable = tables["CheckBox"]; - var controlTable = tables["Control"]; - - var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false); - - // enumerate through the Control table, adding CheckBox values where appropriate - if (null != controlTable) - { - foreach (var row in controlTable.Rows) - { - var xControl = this.GetIndexedElement(row); - - if ("CheckBox" == row.FieldAsString(2)) - { - var property = row.FieldAsString(8); - if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow)) - { - // if we've seen this property already, create a reference to it - if (checkBoxProperties.TryGetValue(property, out var seen) && seen) - { - xControl.SetAttributeValue("CheckBoxPropertyRef", property); - } - else - { - xControl.SetAttributeValue("Property", property); - checkBoxProperties[property] = true; - } - - xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox")); - } - } - } - } - } - - /// - /// Finalize the Component table. - /// - /// The collection of all tables. - /// - /// Set the keypaths for each component. - /// - private void FinalizeComponentTable(TableIndexedCollection tables) - { - var componentTable = tables["Component"]; - var fileTable = tables["File"]; - var odbcDataSourceTable = tables["ODBCDataSource"]; - var registryTable = tables["Registry"]; - - // set the component keypaths - if (null != componentTable) - { - foreach (var row in componentTable.Rows) - { - var attributes = row.FieldAsInteger(3); - var keyPath = row.FieldAsString(5); - - if (String.IsNullOrEmpty(keyPath)) - { - var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0)); - xComponent.SetAttributeValue("KeyPath", "yes"); - } - else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) - { - if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath)) - { - if (xRegistry.Name.LocalName == "RegistryValue") - { - xRegistry.SetAttributeValue("KeyPath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath)); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry")); - } - } - else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)) - { - if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath)) - { - xOdbcDataSource.SetAttributeValue("KeyPath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource")); - } - } - else - { - if (this.TryGetIndexedElement("File", out var xFile, keyPath)) - { - xFile.SetAttributeValue("KeyPath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File")); - } - } - } - } - - // add the File children elements - if (null != fileTable) - { - foreach (FileRow fileRow in fileTable.Rows) - { - if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component) - && this.TryGetIndexedElement(fileRow, out var xFile)) - { - xComponent.Add(xFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); - } - } - } - - // add the ODBCDataSource children elements - if (null != odbcDataSourceTable) - { - foreach (var row in odbcDataSourceTable.Rows) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1)) - && this.TryGetIndexedElement(row, out var xOdbcDataSource)) - { - xComponent.Add(xOdbcDataSource); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); - } - } - } - - // add the Registry children elements - if (null != registryTable) - { - foreach (var row in registryTable.Rows) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5)) - && this.TryGetIndexedElement(row, out var xRegistry)) - { - xComponent.Add(xRegistry); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component")); - } - } - } - } - - /// - /// Finalize the Dialog table. - /// - /// The collection of all tables. - /// - /// Sets the first, default, and cancel control for each dialog and adds all child control - /// elements to the dialog. - /// - private void FinalizeDialogTable(TableIndexedCollection tables) - { - // if the user has requested to suppress the UI elements, we have nothing to do - if (this.SuppressUI) - { - return; - } - - var addedControls = new HashSet(); - - var controlTable = tables["Control"]; - var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - - var dialogTable = tables["Dialog"]; - if (null != dialogTable) - { - foreach (var dialogRow in dialogTable.Rows) - { - var xDialog = this.GetIndexedElement(dialogRow); - var dialogId = dialogRow.FieldAsString(0); - - if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7))) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control")); - } - - // add tabbable controls - while (null != xControl) - { - var controlId = xControl.Attribute("Id"); - var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)]; - - xControl.SetAttributeValue("TabSkip", "no"); - - xDialog.Add(xControl); - addedControls.Add(xControl); - - var controlNext = controlRow.FieldAsString(10); - if (!String.IsNullOrEmpty(controlNext)) - { - if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext)) - { - // looped back to the first control in the dialog - if (addedControls.Contains(xControl)) - { - xControl = null; - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control")); - } - } - else - { - xControl = null; - } - } - - // set default control - var controlDefault = dialogRow.FieldAsString(8); - if (!String.IsNullOrEmpty(controlDefault)) - { - if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault)) - { - xDefaultControl.SetAttributeValue("Default", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control")); - } - } - - // set cancel control - var controlCancel = dialogRow.FieldAsString(8); - if (!String.IsNullOrEmpty(controlCancel)) - { - if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel)) - { - xCancelControl.SetAttributeValue("Cancel", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control")); - } - } - } - } - - // add the non-tabbable controls to the dialog - if (null != controlTable) - { - foreach (var controlRow in controlTable.Rows) - { - var dialogId = controlRow.FieldAsString(0); - if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog")); - continue; - } - - var xControl = this.GetIndexedElement(controlRow); - if (!addedControls.Contains(xControl)) - { - xControl.SetAttributeValue("TabSkip", "yes"); - xDialog.Add(xControl); - } - } - } - } - - /// - /// Finalize the DuplicateFile and MoveFile tables. - /// - /// The collection of all tables. - /// - /// Sets the source/destination property/directory for each DuplicateFile or - /// MoveFile row. - /// - private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) - { - var duplicateFileTable = tables["DuplicateFile"]; - if (null != duplicateFileTable) - { - foreach (var row in duplicateFileTable.Rows) - { - var xCopyFile = this.GetIndexedElement(row); - var destination = row.FieldAsString(4); - if (!String.IsNullOrEmpty(destination)) - { - if (this.TryGetIndexedElement("Directory", out var _, destination)) - { - xCopyFile.SetAttributeValue("DestinationDirectory", destination); - } - else - { - xCopyFile.SetAttributeValue("DestinationProperty", destination); - } - } - } - } - - var moveFileTable = tables["MoveFile"]; - if (null != moveFileTable) - { - foreach (var row in moveFileTable.Rows) - { - var xCopyFile = this.GetIndexedElement(row); - var source = row.FieldAsString(4); - if (!String.IsNullOrEmpty(source)) - { - if (this.TryGetIndexedElement("Directory", out var _, source)) - { - xCopyFile.SetAttributeValue("SourceDirectory", source); - } - else - { - xCopyFile.SetAttributeValue("SourceProperty", source); - } - } - - var destination = row.FieldAsString(5); - if (this.TryGetIndexedElement("Directory", out var _, destination)) - { - xCopyFile.SetAttributeValue("DestinationDirectory", destination); - } - else - { - xCopyFile.SetAttributeValue("DestinationProperty", destination); - } - } - } - } - - /// - /// Finalize the FamilyFileRanges table. - /// - /// The collection of all tables. - private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) - { - var familyFileRangesTable = tables["FamilyFileRanges"]; - if (null != familyFileRangesTable) - { - foreach (var row in familyFileRangesTable.Rows) - { - var xProtectRange = new XElement(Names.ProtectRangeElement); - - if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) - { - var retainOffsets = row.FieldAsString(2).Split(','); - var retainLengths = row.FieldAsString(3).Split(','); - - if (retainOffsets.Length == retainLengths.Length) - { - for (var i = 0; i < retainOffsets.Length; i++) - { - if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16)); - } - else - { - xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture)); - } - - if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16)); - } - else - { - xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture)); - } - } - } - else - { - // TODO: warn - } - } - else if (!row.IsColumnNull(2) || !row.IsColumnNull(3)) - { - // TODO: warn about mismatch between columns - } - - this.IndexElement(row, xProtectRange); - } - } - - var usedProtectRanges = new HashSet(); - var externalFilesTable = tables["ExternalFiles"]; - if (null != externalFilesTable) - { - foreach (var row in externalFilesTable.Rows) - { - if (this.TryGetIndexedElement(row, out var xExternalFile) - && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0))) - { - xExternalFile.Add(xProtectRange); - usedProtectRanges.Add(xProtectRange); - } - } - } - - var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; - if (null != targetFiles_OptionalDataTable) - { - var targetImagesTable = tables["TargetImages"]; - var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); - - var upgradedImagesTable = tables["UpgradedImages"]; - var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); - - foreach (var row in targetFiles_OptionalDataTable.Rows) - { - var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; - - if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); - continue; - } - - if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages")); - continue; - } - - if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1))) - { - xTargetFile.Add(xProtectRange); - usedProtectRanges.Add(xProtectRange); - } - } - } - - if (null != familyFileRangesTable) - { - foreach (var row in familyFileRangesTable.Rows) - { - var xProtectRange = this.GetIndexedElement(row); - - if (!usedProtectRanges.Contains(xProtectRange)) - { - var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1))); - xProtectFile.Add(xProtectRange); - - this.AddChildToParent("ImageFamilies", xProtectFile, row, 0); - } - } - } - } - - /// - /// Finalize the FeatureComponents table. - /// - /// The collection of all tables. - /// - /// Since tables specifying references to the FeatureComponents table have references to - /// the Feature and Component table separately, but not the FeatureComponents table specifically, - /// the FeatureComponents table and primary features must be decompiled during finalization. - /// - private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) - { - var classTable = tables["Class"]; - if (null != classTable) - { - foreach (var row in classTable.Rows) - { - this.SetPrimaryFeature(row, 11, 2); - } - } - - var extensionTable = tables["Extension"]; - if (null != extensionTable) - { - foreach (var row in extensionTable.Rows) - { - this.SetPrimaryFeature(row, 4, 1); - } - } - - var msiAssemblyTable = tables["MsiAssembly"]; - if (null != msiAssemblyTable) - { - foreach (var row in msiAssemblyTable.Rows) - { - this.SetPrimaryFeature(row, 1, 0); - } - } - - var publishComponentTable = tables["PublishComponent"]; - if (null != publishComponentTable) - { - foreach (var row in publishComponentTable.Rows) - { - this.SetPrimaryFeature(row, 4, 2); - } - } - - var typeLibTable = tables["TypeLib"]; - if (null != typeLibTable) - { - foreach (var row in typeLibTable.Rows) - { - this.SetPrimaryFeature(row, 6, 2); - } - } - } - - /// - /// Finalize the File table. - /// - /// The collection of all tables. - /// - /// Sets the source, diskId, and assembly information for each file. - /// - private void FinalizeFileTable(TableIndexedCollection tables) - { - // index the media table by media id - var mediaTable = tables["Media"]; - var mediaRows = new RowDictionary(mediaTable); - - // set the disk identifiers and sources for files - foreach (var fileRow in tables["File"]?.Rows.Cast() ?? Enumerable.Empty()) - { - var xFile = this.GetIndexedElement("File", fileRow.File); - - // Don't bother processing files that are orphaned (and won't show up in the output anyway) - if (null != xFile.Parent) - { - // set the diskid - if (null != mediaTable) - { - foreach (MediaRow mediaRow in mediaTable.Rows) - { - if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) - { - xFile.SetAttributeValue("DiskId", mediaRow.DiskId); - break; - } - } - } - - var fileId = xFile?.Attribute("Id")?.Value; - var fileCompressed = xFile?.Attribute("Compressed")?.Value; - var fileShortName = xFile?.Attribute("ShortName")?.Value; - var fileName = xFile?.Attribute("Name")?.Value; - - // set the source (done here because it requires information from the Directory table) - if (OutputType.Module == this.OutputType) - { - xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_'))); - } - else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) - { - xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId)); - } - else // uncompressed - { - var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName; - - if (this.Compressed) // uncompressed at the root of the source image - { - xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name)); - } - else - { - var sourcePath = this.GetSourcePath(xFile); - xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name)); - } - } - } - } - - // set the file assemblies and manifests - foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty()) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) - { - foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) - { - xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2)); - xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3)); - xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32"); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); - } - } - - // nest the TypeLib elements - foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty()) - { - var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2)); - var xTypeLib = this.GetIndexedElement(row); - - foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) - { - xFile.Add(xTypeLib); - } - } - } - - /// - /// Finalize the MIME table. - /// - /// The collection of all tables. - /// - /// There is a foreign key shared between the MIME and Extension - /// tables so either one would be valid to be decompiled first, so - /// the only safe way to nest the MIME elements is to do it during finalize. - /// - private void FinalizeMIMETable(TableIndexedCollection tables) - { - var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty(); - foreach (var row in extensionRows) - { - // set the default MIME element for this extension - var mimeRef = row.FieldAsString(3); - if (null != mimeRef) - { - if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef)) - { - xMime.SetAttributeValue("Default", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); - } - } - } - - var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows); - - foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty()) - { - var xMime = this.GetIndexedElement(row); - - if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions)) - { - foreach (var extension in xExtensions) - { - extension.Add(xMime); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension")); - } - } - } - - /// - /// Finalize the ProgId table. - /// - /// The collection of all tables. - /// - /// Enumerates through all the Class rows, looking for child ProgIds (these are the - /// default ProgIds for a given Class). Then go through the ProgId table and add any - /// remaining ProgIds for each Class. This happens during finalize because there is - /// a circular dependency between the Class and ProgId tables. - /// - private void FinalizeProgIdTable(TableIndexedCollection tables) - { - // add the default ProgIds for each class (and index the class table) - var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty(); - - var classesByCLSID = this.IndexTableOneToMany(classRows); - - var addedProgIds = new Dictionary(); - - foreach (var row in classRows) - { - var clsid = row.FieldAsString(0); - var xClass = this.GetIndexedElement(row); - - if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3))) - { - if (addedProgIds.TryGetValue(xProgId, out var progid)) - { - this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid)); - } - else - { - xClass.Add(xProgId); - addedProgIds.Add(xProgId, clsid); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId")); - } - } - - // add the remaining non-default ProgId entries for each class - foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty()) - { - var clsid = row.FieldAsString(2); - var xProgId = this.GetIndexedElement(row); - - if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent) - { - if (classesByCLSID.TryGetValue(clsid, out var xClasses)) - { - foreach (var xClass in xClasses) - { - xClass.Add(xProgId); - addedProgIds.Add(xProgId, clsid); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class")); - } - } - } - - // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension - var componentsById = this.IndexTableOneToMany(tables, "Component"); - - foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty()) - { - var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2)); - - // Haven't added the progId yet and it doesn't have a parent progId - if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent) - { - if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents)) - { - foreach (var xComponent in xComponents) - { - xComponent.Add(xProgId); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); - } - } - } - } - - /// - /// Finalize the Property table. - /// - /// The collection of all tables. - /// - /// Removes properties that are generated from other entries. - /// - private void FinalizePropertyTable(TableIndexedCollection tables) - { - foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty()) - { - // If no other fields on the property are set we must have created it in the backend. - var bits = row.FieldAsInteger(1); - if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) - && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript) - && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0)) - && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value) - && xProperty.Attribute("Secure")?.Value != "yes" - && xProperty.Attribute("SuppressModularization")?.Value != "yes") - { - xProperty.Remove(); - } - } - } - - /// - /// Finalize the RemoveFile table. - /// - /// The collection of all tables. - /// - /// Sets the directory/property for each RemoveFile row. - /// - private void FinalizeRemoveFileTable(TableIndexedCollection tables) - { - foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty()) - { - var xRemove = this.GetIndexedElement(row); - var property = row.FieldAsString(3); - - if (this.TryGetIndexedElement("Directory", out var _, property)) - { - xRemove.SetAttributeValue("Directory", property); - } - else - { - xRemove.SetAttributeValue("Property", property); - } - } - } - - /// - /// Finalize the LockPermissions or MsiLockPermissionsEx table. - /// - /// The collection of all tables. - /// Which table to finalize. - /// - /// Nests the Permission elements below their parent elements. There are no declared foreign - /// keys for the parents of the LockPermissions table. - /// - private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName) - { - var createFoldersById = this.IndexTableOneToMany(tables, tableName); - - foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty()) - { - var id = row.FieldAsString(0); - var table = row.FieldAsString(1); - var xPermission = this.GetIndexedElement(row); - - if ("CreateFolder" == table) - { - if (createFoldersById.TryGetValue(id, out var xCreateFolders)) - { - foreach (var xCreateFolder in xCreateFolders) - { - xCreateFolder.Add(xPermission); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - else - { - if (this.TryGetIndexedElement(table, out var xParent, id)) - { - xParent.Add(xPermission); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - } - } - - /// - /// Finalize the LockPermissions table. - /// - /// The collection of all tables. - /// - /// Nests the Permission elements below their parent elements. There are no declared foreign - /// keys for the parents of the LockPermissions table. - /// - private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions"); - - /// - /// Finalize the MsiLockPermissionsEx table. - /// - /// The collection of all tables. - /// - /// Nests the PermissionEx elements below their parent elements. There are no declared foreign - /// keys for the parents of the MsiLockPermissionsEx table. - /// - private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx"); - - private static Dictionary> IndexTable(Table table, int keyColumn, int? dataColumn) - { - if (table == null) - { - return new Dictionary>(); - } - - return table.Rows - .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null) - .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); - } - - private static XElement FindComplianceDrive(XElement xSearch) - { - var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement); - if (null == xComplianceDrive) - { - xComplianceDrive = new XElement(Names.ComplianceDriveElement); - xSearch.Add(xComplianceDrive); - } - - return xComplianceDrive; - } - - /// - /// Finalize the search tables. - /// - /// The collection of all tables. - /// Does all the complex linking required for the search tables. - private void FinalizeSearchTables(TableIndexedCollection tables) - { - var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0); - var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null); - var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row); - - var xComplianceCheck = new XElement(Names.ComplianceCheckElement); - if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature))) - { - this.RootElement.Add(xComplianceCheck); - } - - // index the locator tables by their signatures - var locators = - new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" } - .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty()) - .ToLookup(row => row.FieldAsString(0), row => row) - .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); - - // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) - foreach (var locatorRows in locators.Values) - { - var firstDrLocator = -1; - - for (var i = 0; i < locatorRows.Count; i++) - { - var locatorRow = (Row)locatorRows[i]; - - if ("DrLocator" == locatorRow.TableDefinition.Name) - { - if (-1 == firstDrLocator) - { - firstDrLocator = i; - } - - if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) - { - locatorRows.RemoveAt(i); - locatorRows.Insert(firstDrLocator, locatorRow); - break; - } - } - } - } - - var xUsedSearches = new HashSet(); - var xUnusedSearches = new Dictionary(); - - foreach (var signature in locators.Keys) - { - var locatorRows = locators[signature]; - var xSignatureSearches = new List(); - - foreach (var locatorRow in locatorRows) - { - var used = true; - var xSearch = this.GetIndexedElement(locatorRow); - - if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count) - { - foreach (var xSearchParent in xSignatureSearches) - { - if (!xUsedSearches.Contains(xSearch)) - { - xSearchParent.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else - { - var xFileSearchRef = new XElement(Names.FileSearchRefElement, - new XAttribute("Id", signature)); - - xSearchParent.Add(xFileSearchRef); - } - } - } - else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1)) - { - var parentSignature = locatorRow.FieldAsString(1); - - if ("CCP_DRIVE" == parentSignature) - { - if (appSearches.ContainsKey(signature) - && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) - { - foreach (var propertyId in appSearchPropertyIds) - { - var xProperty = this.EnsureProperty(propertyId); - - if (ccpSearches.ContainsKey(signature)) - { - xProperty.SetAttributeValue("ComplianceCheck", "yes"); - } - - var xComplianceDrive = FindComplianceDrive(xProperty); - - if (!xUsedSearches.Contains(xSearch)) - { - xComplianceDrive.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else - { - var directorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", signature), - XAttributeIfNotNull("Parent", locatorRow, 1), - XAttributeIfNotNull("Path", locatorRow, 2)); - - xComplianceDrive.Add(directorySearchRef); - xSignatureSearches.Add(directorySearchRef); - } - } - } - else if (ccpSearches.ContainsKey(signature)) - { - var xComplianceDrive = FindComplianceDrive(xComplianceCheck); - - if (!xUsedSearches.Contains(xSearch)) - { - xComplianceDrive.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else - { - var directorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", signature), - XAttributeIfNotNull("Parent", locatorRow, 1), - XAttributeIfNotNull("Path", locatorRow, 2)); - - xComplianceDrive.Add(directorySearchRef); - xSignatureSearches.Add(directorySearchRef); - } - } - } - else - { - var usedDrLocator = false; - - if (locators.TryGetValue(parentSignature, out var parentLocatorRows)) - { - foreach (var parentLocatorRow in parentLocatorRows) - { - if ("DrLocator" == parentLocatorRow.TableDefinition.Name) - { - var xParentSearch = this.GetIndexedElement(parentLocatorRow); - - if (xParentSearch.HasElements) - { - var parentDrLocatorRow = drLocators[xParentSearch]; - var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", parentSignature), - XAttributeIfNotNull("Parent", parentDrLocatorRow, 1), - XAttributeIfNotNull("Path", parentDrLocatorRow, 2)); - - xParentSearch = xDirectorySearchRef; - xUnusedSearches.Add(parentSignature, xDirectorySearchRef); - } - - if (!xUsedSearches.Contains(xSearch)) - { - xParentSearch.Add(xSearch); - xUsedSearches.Add(xSearch); - usedDrLocator = true; - } - else - { - var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", signature), - new XAttribute("Parent", parentSignature), - XAttributeIfNotNull("Path", locatorRow, 2)); - - xParentSearch.Add(xSearch); - usedDrLocator = true; - } - } - else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) - { - var xParentSearch = this.GetIndexedElement(parentLocatorRow); - - xParentSearch.Add(xSearch); - xUsedSearches.Add(xSearch); - usedDrLocator = true; - } - } - - // keep track of unused DrLocator rows - if (!usedDrLocator) - { - xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); - } - } - else - { - // TODO: warn - } - } - } - else if (appSearches.ContainsKey(signature) - && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) - { - foreach (var propertyId in appSearchPropertyIds) - { - var xProperty = this.EnsureProperty(propertyId); - - if (ccpSearches.ContainsKey(signature)) - { - xProperty.SetAttributeValue("ComplianceCheck", "yes"); - } - - if (!xUsedSearches.Contains(xSearch)) - { - xProperty.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else if ("RegLocator" == locatorRow.TableDefinition.Name) - { - var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, - new XAttribute("Id", signature)); - - xProperty.Add(xRegistrySearchRef); - xSignatureSearches.Add(xRegistrySearchRef); - } - else - { - // TODO: warn about unavailable Ref element - } - } - } - else if (ccpSearches.ContainsKey(signature)) - { - if (!xUsedSearches.Contains(xSearch)) - { - xComplianceCheck.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else if ("RegLocator" == locatorRow.TableDefinition.Name) - { - var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, - new XAttribute("Id", signature)); - - xComplianceCheck.Add(xRegistrySearchRef); - xSignatureSearches.Add(xRegistrySearchRef); - } - else - { - // TODO: warn about unavailable Ref element - } - } - else - { - if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch") - { - xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); - } - else - { - // TODO: warn - used = false; - } - } - - // keep track of the search elements for this signature so that nested searches go in the proper parents - if (used) - { - xSignatureSearches.Add(xSearch); - } - } - } - - // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. - foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key)) - { - var used = false; - - XElement xLeafDirectorySearch = null; - var xUnusedSearch = unusedSearch.Value; - var xParent = xUnusedSearch; - var updatedLeaf = true; - while (updatedLeaf) - { - updatedLeaf = false; - - var xDirectorySearch = xParent.Element(Names.DirectorySearchElement); - if (xDirectorySearch != null) - { - xParent = xLeafDirectorySearch = xDirectorySearch; - updatedLeaf = true; - } - } - - if (xLeafDirectorySearch != null) - { - var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value; - if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds)) - { - var xProperty = this.EnsureProperty(appSearchPropertyIds[0]); - xProperty.Add(xUnusedSearch); - used = true; - } - else if (ccpSearches.ContainsKey(leafDirectorySearchId)) - { - xComplianceCheck.Add(xUnusedSearch); - used = true; - } - else - { - // TODO: warn - } - } - - if (!used) - { - // TODO: warn - } - } - } - - /// - /// Finalize the Shortcut table. - /// - /// The collection of all tables. - /// - /// Sets Advertise to yes if Target points to a Feature. - /// Occurs during finalization because it has to check against every feature row. - /// - private void FinalizeShortcutTable(TableIndexedCollection tables) - { - var shortcutTable = tables["Shortcut"]; - if (null == shortcutTable) - { - return; - } - - foreach (var row in shortcutTable.Rows) - { - var xShortcut = this.GetIndexedElement(row); - - var target = row.FieldAsString(4); - - if (this.TryGetIndexedElement("Feature", out var _, target)) - { - xShortcut.SetAttributeValue("Advertise", "yes"); - this.SetPrimaryFeature(row, 4, 3); - } - else - { - // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element - xShortcut.SetAttributeValue("Target", target); - } - } - } - - /// - /// Finalize the sequence tables. - /// - /// The collection of all tables. - /// - /// Creates the sequence elements. Occurs during finalization because its - /// not known if sequences refer to custom actions or dialogs during decompilation. - /// - private void FinalizeSequenceTables(TableIndexedCollection tables) - { - // finalize the normal sequence tables - if (OutputType.Product == this.OutputType && !this.TreatProductAsModule) - { - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = sequenceTable.WindowsInstallerTableName(); - - // if suppressing UI elements, skip UI-related sequence tables - if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) - { - continue; - } - - var table = tables[sequenceTableName]; - - if (null != table) - { - var actionSymbols = new List(); - var needAbsoluteScheduling = this.SuppressRelativeActionSequencing; - var nonSequencedActionRows = new Dictionary(); - var suppressedRelativeActionRows = new Dictionary(); - - // create a sorted array of actions in this table - foreach (var row in table.Rows) - { - var action = row.FieldAsString(0); - var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action)); - - actionSymbol.Action = action; - - if (!row.IsColumnNull(1)) - { - actionSymbol.Condition = row.FieldAsString(1); - } - - actionSymbol.Sequence = row.FieldAsInteger(2); - - actionSymbol.SequenceTable = sequenceTable; - - actionSymbols.Add(actionSymbol); - } - actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList(); - - for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++) - { - var actionSymbol = actionSymbols[i]; - this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow); - - // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions - if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition) - { - WixActionSymbol previousActionSymbol = null; - WixActionSymbol nextActionSymbol = null; - - // find the previous action row if there is one - if (0 <= i - 1) - { - previousActionSymbol = actionSymbols[i - 1]; - } - - // find the next action row if there is one - if (actionSymbols.Count > i + 1) - { - nextActionSymbol = actionSymbols[i + 1]; - } - - // the logic for setting the before or after attribute for an action: - // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. - // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. - // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. - // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. - // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. - // 6. If this action is AppSearch and has all standard information, ignore it. - // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. - // 8. Everything must be absolutely sequenced. - if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence)) - { - needAbsoluteScheduling = true; - } - else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) - { - actionSymbol.Before = nextActionSymbol.Action; - } - else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence) - { - actionSymbol.After = previousActionSymbol.Action; - } - else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action) - { - actionSymbol.After = previousActionSymbol.Action; - } - else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) - { - actionSymbol.Before = nextActionSymbol.Action; - } - else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition) - { - // ignore an AppSearch row which has the WiX standard sequence and a standard condition - } - else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers - { - nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol); - } - else if (0 < actionSymbol.Sequence) - { - needAbsoluteScheduling = true; - } - } - else - { - suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol); - } - } - - // create the actions now that we know if they must be absolutely or relatively scheduled - foreach (var actionRow in actionSymbols) - { - var key = actionRow.Id.Id; - - if (needAbsoluteScheduling) - { - // remove any before/after information to ensure this is absolutely sequenced - actionRow.Before = null; - actionRow.After = null; - } - else if (nonSequencedActionRows.ContainsKey(key)) - { - // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) - actionRow.Sequence = 0; - } - else if (suppressedRelativeActionRows.ContainsKey(key)) - { - // skip the suppressed relatively scheduled action rows - continue; - } - - // create the action element - this.CreateActionElement(actionRow); - } - } - } - } - else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables - { - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = sequenceTable.WindowsInstallerTableName(); - - // if suppressing UI elements, skip UI-related sequence tables - if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) - { - continue; - } - - var table = tables[String.Concat("Module", sequenceTableName)]; - - if (null != table) - { - foreach (var row in table.Rows) - { - var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0))); - - actionRow.Action = row.FieldAsString(0); - - if (!row.IsColumnNull(1)) - { - actionRow.Sequence = row.FieldAsInteger(1); - } - - if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) - { - switch (row.FieldAsInteger(3)) - { - case 0: - actionRow.Before = row.FieldAsString(2); - break; - case 1: - actionRow.After = row.FieldAsString(2); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); - break; - } - } - - if (!row.IsColumnNull(4)) - { - actionRow.Condition = row.FieldAsString(4); - } - - actionRow.SequenceTable = sequenceTable; - - // create action elements for non-standard actions - if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before) - { - this.CreateActionElement(actionRow); - } - } - } - } - } - } - - /// - /// Finalize the Upgrade table. - /// - /// The collection of all tables. - /// - /// Decompile the rows from the Upgrade and LaunchCondition tables - /// created by the MajorUpgrade element. - /// - private void FinalizeUpgradeTable(TableIndexedCollection tables) - { - var launchConditionTable = tables["LaunchCondition"]; - var upgradeTable = tables["Upgrade"]; - string downgradeErrorMessage = null; - string disallowUpgradeErrorMessage = null; - - // find the DowngradePreventedCondition launch condition message - if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) - { - foreach (var launchRow in launchConditionTable.Rows) - { - if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0])) - { - downgradeErrorMessage = Convert.ToString(launchRow[1]); - } - else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0])) - { - disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); - } - } - } - - if (null != upgradeTable && 0 < upgradeTable.Rows.Count) - { - XElement xMajorUpgrade = null; - - foreach (UpgradeRow upgradeRow in upgradeTable.Rows) - { - if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty) - { - var attr = upgradeRow.Attributes; - var removeFeatures = upgradeRow.Remove; - xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); - - if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) - { - xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) - { - xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) - { - xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes"); - } - - if (!String.IsNullOrEmpty(removeFeatures)) - { - xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures); - } - } - else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) - { - xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); - xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage); - } - } - - if (xMajorUpgrade != null) - { - if (String.IsNullOrEmpty(downgradeErrorMessage)) - { - xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes"); - } - - if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - xMajorUpgrade.SetAttributeValue("Disallow", "yes"); - xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage); - } - - var scheduledType = DetermineMajorUpgradeScheduling(tables); - if (scheduledType != "afterInstallValidate") - { - xMajorUpgrade.SetAttributeValue("Schedule", scheduledType); - } - - this.RootElement.Add(xMajorUpgrade); - } - } - } - - /// - /// Finalize the Verb table. - /// - /// The collection of all tables. - /// - /// The Extension table is a foreign table for the Verb table, but the - /// foreign key is only part of the primary key of the Extension table, - /// so it needs special logic to be nested properly. - /// - private void FinalizeVerbTable(TableIndexedCollection tables) - { - var xExtensions = this.IndexTableOneToMany(tables["Extension"]); - - var verbTable = tables["Verb"]; - if (null != verbTable) - { - foreach (var row in verbTable.Rows) - { - if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions)) - { - var xVerb = this.GetIndexedElement(row); - - foreach (var xVerbExtension in xVerbExtensions) - { - xVerbExtension.Add(xVerb); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension")); - } - } - } - } - - /// - /// Get the path to a file in the source image. - /// - /// The file. - /// The path to the file in the source image. - private string GetSourcePath(XElement xFile) - { - var sourcePath = new StringBuilder(); - - var component = xFile.Parent; - - for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent) - { - string name; - - var dirSourceName = xDirectory.Attribute("SourceName")?.Value; - var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value; - var dirShortName = xDirectory.Attribute("ShortName")?.Value; - var dirName = xDirectory.Attribute("Name")?.Value; - - if (!this.ShortNames && null != dirSourceName) - { - name = dirSourceName; - } - else if (null != dirShortSourceName) - { - name = dirShortSourceName; - } - else if (!this.ShortNames || null == dirShortName) - { - name = dirName; - } - else - { - name = dirShortName; - } - - if (0 == sourcePath.Length) - { - sourcePath.Append(name); - } - else - { - sourcePath.Insert(0, Path.DirectorySeparatorChar); - sourcePath.Insert(0, name); - } - } - - return sourcePath.ToString(); - } - - /// - /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). - /// - /// The name of the table to resolve. - /// The unsorted table names. - /// The sorted table names. - private void ResolveTableDependencies(string tableName, List unsortedTableNames, HashSet sortedTableNames) - { - unsortedTableNames.Remove(tableName); - - foreach (var columnDefinition in this.TableDefinitions[tableName].Columns) - { - // no dependency to resolve because this column doesn't reference another table - if (null == columnDefinition.KeyTable) - { - continue; - } - - foreach (var keyTable in columnDefinition.KeyTable.Split(';')) - { - if (tableName == keyTable) - { - continue; // self-referencing dependency - } - else if (sortedTableNames.Contains(keyTable)) - { - continue; // dependent table has already been sorted - } - else if (!this.TableDefinitions.Contains(keyTable)) - { - this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); - } - else if (unsortedTableNames.Contains(keyTable)) - { - this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); - } - else - { - // found a circular dependency, so ignore it (this assumes that the tables will - // use a finalize method to nest their elements since the ordering will not be - // deterministic - } - } - } - - sortedTableNames.Add(tableName); - } - - /// - /// Get the names of the tables to process in the order they should be processed, according to their dependencies. - /// - /// A StringCollection containing the ordered table names. - private HashSet GetOrderedTableNames() - { - var orderedTableNames = new HashSet(); - var unsortedTableNames = new List(this.TableDefinitions.Select(t => t.Name)); - - // resolve the dependencies for each table - while (0 < unsortedTableNames.Count) - { - this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames); - } - - return orderedTableNames; - } - - /// - /// Initialize decompilation. - /// - /// The collection of all tables. - /// - private void InitializeDecompile(TableIndexedCollection tables, int codepage) - { - // reset all the state information - this.Compressed = false; - this.ShortNames = false; - - this.Singletons.Clear(); - this.IndexedElements.Clear(); - this.PatchTargetFiles.Clear(); - - // set the codepage if its not neutral (0) - if (0 != codepage) - { - this.RootElement.SetAttributeValue("Codepage", codepage); - } - - if (this.OutputType == OutputType.Module) - { - var table = tables["_SummaryInformation"]; - var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9); - this.ModularizationGuid = row?.FieldAsString(1); - } - - // index the rows from the extension libraries - var indexedExtensionTables = new Dictionary>(); -#if TODO_DECOMPILER_EXTENSIONS - foreach (IDecompilerExtension extension in this.extensions) - { - // Get the optional library from the extension with the rows to be removed. - Library library = extension.GetLibraryToRemove(this.tableDefinitions); - if (null != library) - { - foreach (var section in library.Sections) - { - foreach (Table table in section.Tables) - { - foreach (Row row in table.Rows) - { - string primaryKey; - string tableName; - - // the Actions table needs to be handled specially - if ("WixAction" == table.Name) - { - primaryKey = row.FieldAsString(1); - - if (OutputType.Module == this.outputType) - { - tableName = String.Concat("Module", row.FieldAsString(0)); - } - else - { - tableName = row.FieldAsString(0); - } - } - else - { - primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); - tableName = table.Name; - } - - if (null != primaryKey) - { - HashSet indexedExtensionRows; - if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) - { - indexedExtensionRows = new HashSet(); - indexedExtensionTables.Add(tableName, indexedExtensionRows); - } - - indexedExtensionRows.Add(primaryKey); - } - } - } - } - } - } -#endif - - // remove the rows from the extension libraries (to allow full round-tripping) - foreach (var kvp in indexedExtensionTables) - { - var tableName = kvp.Key; - var indexedExtensionRows = kvp.Value; - - var table = tables[tableName]; - if (null != table) - { - var originalRows = new RowDictionary(table); - - // remove the original rows so that they can be added back if they should remain - table.Rows.Clear(); - - foreach (var row in originalRows.Values) - { - if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) - { - table.Rows.Add(row); - } - } - } - } - } - - /// - /// Decompile the tables. - /// - /// The output being decompiled. - private void DecompileTables(WindowsInstallerData output) - { - var orderedTableNames = this.GetOrderedTableNames(); - foreach (var tableName in orderedTableNames) - { - var table = output.Tables[tableName]; - - // table does not exist in this database or should not be decompiled - if (null == table || !this.DecompilableTable(output, tableName)) - { - continue; - } - - this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name)); - - // empty tables may be kept with EnsureTable if the user set the proper option - if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) - { - this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name))); - } - - switch (table.Name) - { - case "_SummaryInformation": - // handled in FinalizeDecompile - break; - case "AdminExecuteSequence": - case "AdminUISequence": - case "AdvtExecuteSequence": - case "InstallExecuteSequence": - case "InstallUISequence": - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - // handled in FinalizeSequenceTables - break; - case "ActionText": - this.DecompileActionTextTable(table); - break; - case "AdvtUISequence": - this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); - break; - case "AppId": - this.DecompileAppIdTable(table); - break; - case "AppSearch": - // handled in FinalizeSearchTables - break; - case "BBControl": - this.DecompileBBControlTable(table); - break; - case "Billboard": - this.DecompileBillboardTable(table); - break; - case "Binary": - this.DecompileBinaryTable(table); - break; - case "BindImage": - this.DecompileBindImageTable(table); - break; - case "CCPSearch": - // handled in FinalizeSearchTables - break; - case "CheckBox": - // handled in FinalizeCheckBoxTable - break; - case "Class": - this.DecompileClassTable(table); - break; - case "ComboBox": - this.DecompileComboBoxTable(table); - break; - case "Control": - this.DecompileControlTable(table); - break; - case "ControlCondition": - this.DecompileControlConditionTable(table); - break; - case "ControlEvent": - this.DecompileControlEventTable(table); - break; - case "CreateFolder": - this.DecompileCreateFolderTable(table); - break; - case "CustomAction": - this.DecompileCustomActionTable(table); - break; - case "CompLocator": - this.DecompileCompLocatorTable(table); - break; - case "Complus": - this.DecompileComplusTable(table); - break; - case "Component": - this.DecompileComponentTable(table); - break; - case "Condition": - this.DecompileConditionTable(table); - break; - case "Dialog": - this.DecompileDialogTable(table); - break; - case "Directory": - this.DecompileDirectoryTable(table); - break; - case "DrLocator": - this.DecompileDrLocatorTable(table); - break; - case "DuplicateFile": - this.DecompileDuplicateFileTable(table); - break; - case "Environment": - this.DecompileEnvironmentTable(table); - break; - case "Error": - this.DecompileErrorTable(table); - break; - case "EventMapping": - this.DecompileEventMappingTable(table); - break; - case "Extension": - this.DecompileExtensionTable(table); - break; - case "ExternalFiles": - this.DecompileExternalFilesTable(table); - break; - case "FamilyFileRanges": - // handled in FinalizeFamilyFileRangesTable - break; - case "Feature": - this.DecompileFeatureTable(table); - break; - case "FeatureComponents": - this.DecompileFeatureComponentsTable(table); - break; - case "File": - this.DecompileFileTable(table); - break; - case "FileSFPCatalog": - this.DecompileFileSFPCatalogTable(table); - break; - case "Font": - this.DecompileFontTable(table); - break; - case "Icon": - this.DecompileIconTable(table); - break; - case "ImageFamilies": - this.DecompileImageFamiliesTable(table); - break; - case "IniFile": - this.DecompileIniFileTable(table); - break; - case "IniLocator": - this.DecompileIniLocatorTable(table); - break; - case "IsolatedComponent": - this.DecompileIsolatedComponentTable(table); - break; - case "LaunchCondition": - this.DecompileLaunchConditionTable(table); - break; - case "ListBox": - this.DecompileListBoxTable(table); - break; - case "ListView": - this.DecompileListViewTable(table); - break; - case "LockPermissions": - this.DecompileLockPermissionsTable(table); - break; - case "Media": - this.DecompileMediaTable(table); - break; - case "MIME": - this.DecompileMIMETable(table); - break; - case "ModuleAdvtUISequence": - this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); - break; - case "ModuleComponents": - // handled by DecompileComponentTable (since the ModuleComponents table - // rows are created by nesting components under the Module element) - break; - case "ModuleConfiguration": - this.DecompileModuleConfigurationTable(table); - break; - case "ModuleDependency": - this.DecompileModuleDependencyTable(table); - break; - case "ModuleExclusion": - this.DecompileModuleExclusionTable(table); - break; - case "ModuleIgnoreTable": - this.DecompileModuleIgnoreTableTable(table); - break; - case "ModuleSignature": - this.DecompileModuleSignatureTable(table); - break; - case "ModuleSubstitution": - this.DecompileModuleSubstitutionTable(table); - break; - case "MoveFile": - this.DecompileMoveFileTable(table); - break; - case "MsiAssembly": - // handled in FinalizeFileTable - break; - case "MsiDigitalCertificate": - this.DecompileMsiDigitalCertificateTable(table); - break; - case "MsiDigitalSignature": - this.DecompileMsiDigitalSignatureTable(table); - break; - case "MsiEmbeddedChainer": - this.DecompileMsiEmbeddedChainerTable(table); - break; - case "MsiEmbeddedUI": - this.DecompileMsiEmbeddedUITable(table); - break; - case "MsiLockPermissionsEx": - this.DecompileMsiLockPermissionsExTable(table); - break; - case "MsiPackageCertificate": - this.DecompileMsiPackageCertificateTable(table); - break; - case "MsiPatchCertificate": - this.DecompileMsiPatchCertificateTable(table); - break; - case "MsiShortcutProperty": - this.DecompileMsiShortcutPropertyTable(table); - break; - case "ODBCAttribute": - this.DecompileODBCAttributeTable(table); - break; - case "ODBCDataSource": - this.DecompileODBCDataSourceTable(table); - break; - case "ODBCDriver": - this.DecompileODBCDriverTable(table); - break; - case "ODBCSourceAttribute": - this.DecompileODBCSourceAttributeTable(table); - break; - case "ODBCTranslator": - this.DecompileODBCTranslatorTable(table); - break; - case "PatchMetadata": - this.DecompilePatchMetadataTable(table); - break; - case "PatchSequence": - this.DecompilePatchSequenceTable(table); - break; - case "ProgId": - this.DecompileProgIdTable(table); - break; - case "Properties": - this.DecompilePropertiesTable(table); - break; - case "Property": - this.DecompilePropertyTable(table); - break; - case "PublishComponent": - this.DecompilePublishComponentTable(table); - break; - case "RadioButton": - this.DecompileRadioButtonTable(table); - break; - case "Registry": - this.DecompileRegistryTable(table); - break; - case "RegLocator": - this.DecompileRegLocatorTable(table); - break; - case "RemoveFile": - this.DecompileRemoveFileTable(table); - break; - case "RemoveIniFile": - this.DecompileRemoveIniFileTable(table); - break; - case "RemoveRegistry": - this.DecompileRemoveRegistryTable(table); - break; - case "ReserveCost": - this.DecompileReserveCostTable(table); - break; - case "SelfReg": - this.DecompileSelfRegTable(table); - break; - case "ServiceControl": - this.DecompileServiceControlTable(table); - break; - case "ServiceInstall": - this.DecompileServiceInstallTable(table); - break; - case "SFPCatalog": - this.DecompileSFPCatalogTable(table); - break; - case "Shortcut": - this.DecompileShortcutTable(table); - break; - case "Signature": - this.DecompileSignatureTable(table); - break; - case "TargetFiles_OptionalData": - this.DecompileTargetFiles_OptionalDataTable(table); - break; - case "TargetImages": - this.DecompileTargetImagesTable(table); - break; - case "TextStyle": - this.DecompileTextStyleTable(table); - break; - case "TypeLib": - this.DecompileTypeLibTable(table); - break; - case "Upgrade": - this.DecompileUpgradeTable(table); - break; - case "UpgradedFiles_OptionalData": - this.DecompileUpgradedFiles_OptionalDataTable(table); - break; - case "UpgradedFilesToIgnore": - this.DecompileUpgradedFilesToIgnoreTable(table); - break; - case "UpgradedImages": - this.DecompileUpgradedImagesTable(table); - break; - case "UIText": - this.DecompileUITextTable(table); - break; - case "Verb": - this.DecompileVerbTable(table); - break; - - default: -#if TODO_DECOMPILER_EXTENSIONS - if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) - { - extension.DecompileTable(table); - } - else -#endif - if (!this.SuppressCustomTables) - { - this.DecompileCustomTable(table); - } - break; - } - } - } - - /// - /// Determine if a particular table should be decompiled with the current settings. - /// - /// The output being decompiled. - /// The name of a table. - /// true if the table should be decompiled; false otherwise. - private bool DecompilableTable(WindowsInstallerData output, string tableName) - { - switch (tableName) - { - case "ActionText": - case "BBControl": - case "Billboard": - case "CheckBox": - case "Control": - case "ControlCondition": - case "ControlEvent": - case "Dialog": - case "Error": - case "EventMapping": - case "RadioButton": - case "TextStyle": - case "UIText": - return !this.SuppressUI; - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleAdvtUISequence": - case "ModuleComponents": - case "ModuleConfiguration": - case "ModuleDependency": - case "ModuleIgnoreTable": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - case "ModuleExclusion": - case "ModuleSignature": - case "ModuleSubstitution": - if (OutputType.Module != output.Type) - { - this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "ExternalFiles": - case "FamilyFileRanges": - case "ImageFamilies": - case "PatchMetadata": - case "PatchSequence": - case "Properties": - case "TargetFiles_OptionalData": - case "TargetImages": - case "UpgradedFiles_OptionalData": - case "UpgradedFilesToIgnore": - case "UpgradedImages": - if (OutputType.PatchCreation != output.Type) - { - this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "MsiPatchHeaders": - case "MsiPatchMetadata": - case "MsiPatchOldAssemblyName": - case "MsiPatchOldAssemblyFile": - case "MsiPatchSequence": - case "Patch": - case "PatchPackage": - this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); - return false; - case "_SummaryInformation": - return true; - case "_Validation": - case "MsiAssemblyName": - case "MsiFileHash": - return false; - default: // all other tables are allowed in any output except for a patch creation package - if (OutputType.PatchCreation == output.Type) - { - this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - } - } - - /// - /// Decompile the _SummaryInformation table. - /// - /// The tables to decompile. - private void FinalizeSummaryInformationStream(TableIndexedCollection tables) - { - var table = tables["_SummaryInformation"]; - - if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) - { - var xSummaryInformation = new XElement(Names.SummaryInformationElement); - - foreach (var row in table.Rows) - { - var value = row.FieldAsString(1); - - if (!String.IsNullOrEmpty(value)) - { - switch (row.FieldAsInteger(0)) - { - case 1: - if ("1252" != value) - { - xSummaryInformation.SetAttributeValue("Codepage", value); - } - break; - case 3: - { - var productName = this.RootElement.Attribute("Name")?.Value; - if (value != productName) - { - xSummaryInformation.SetAttributeValue("Description", value); - } - break; - } - case 4: - { - var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value; - if (value != productManufacturer) - { - xSummaryInformation.SetAttributeValue("Manufacturer", value); - } - break; - } - case 5: - if ("Installer" != value) - { - xSummaryInformation.SetAttributeValue("Keywords", value); - } - break; - case 7: - var template = value.Split(';'); - if (0 < template.Length && 0 < template[template.Length - 1].Length) - { - this.RootElement.SetAttributeValue("Language", template[template.Length - 1]); - } - break; - case 14: - var installerVersion = row.FieldAsInteger(1); - // Default InstallerVersion. - if (installerVersion != 500) - { - this.RootElement.SetAttributeValue("InstallerVersion", installerVersion); - } - break; - case 15: - var wordCount = row.FieldAsInteger(1); - if (0x1 == (wordCount & 0x1)) - { - this.ShortNames = true; - if (OutputType.Product == this.OutputType) - { - this.RootElement.SetAttributeValue("ShortNames", "yes"); - } - } - - if (0x2 == (wordCount & 0x2)) - { - this.Compressed = true; - - if (OutputType.Product == this.OutputType) - { - this.RootElement.SetAttributeValue("Compressed", "yes"); - } - } - - if (OutputType.Product == this.OutputType) - { - if (0x8 == (wordCount & 0x8)) - { - this.RootElement.SetAttributeValue("Scope", "perUser"); - } - else - { - var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS"); - if (xAllUsers?.Attribute("Value")?.Value == "1") - { - xAllUsers?.Remove(); - } - } - } - - break; - } - } - } - - if (xSummaryInformation.HasAttributes) - { - this.RootElement.Add(xSummaryInformation); - } - } - else - { - var xPatchInformation = new XElement(Names.PatchInformationElement); - - foreach (var row in table.Rows) - { - var propertyId = row.FieldAsInteger(0); - var value = row.FieldAsString(1); - - if (!String.IsNullOrEmpty(value)) - { - switch (propertyId) - { - case 1: - if ("1252" != value) - { - xPatchInformation.SetAttributeValue("SummaryCodepage", value); - } - break; - case 3: - xPatchInformation.SetAttributeValue("Description", value); - break; - case 4: - xPatchInformation.SetAttributeValue("Manufacturer", value); - break; - case 5: - if ("Installer,Patching,PCP,Database" != value) - { - xPatchInformation.SetAttributeValue("Keywords", value); - } - break; - case 6: - xPatchInformation.SetAttributeValue("Comments", value); - break; - case 19: - var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); - switch (security) - { - case 0: - xPatchInformation.SetAttributeValue("ReadOnly", "no"); - break; - case 4: - xPatchInformation.SetAttributeValue("ReadOnly", "yes"); - break; - } - break; - } - } - } - - this.RootElement.Add(xPatchInformation); - } - } - - /// - /// Decompile the ActionText table. - /// - /// The table to decompile. - private void DecompileActionTextTable(Table table) - { - foreach (var row in table.Rows) - { - var progressText = new XElement(Names.ProgressTextElement, - new XAttribute("Action", row.FieldAsString(0)), - row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2))); - - this.UIElement.Add(progressText); - } - } - - /// - /// Decompile the AppId table. - /// - /// The table to decompile. - private void DecompileAppIdTable(Table table) - { - foreach (var row in table.Rows) - { - var appId = new XElement(Names.AppIdElement, - new XAttribute("Advertise", "yes"), - new XAttribute("Id", row.FieldAsString(0)), - row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)), - row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)), - row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"), - row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes")); - - this.RootElement.Add(appId); - this.IndexElement(row, appId); - } - } - - /// - /// Decompile the BBControl table. - /// - /// The table to decompile. - private void DecompileBBControlTable(Table table) - { - foreach (BBControlRow bbControlRow in table.Rows) - { - var xControl = new XElement(Names.ControlElement, - new XAttribute("Id", bbControlRow.BBControl), - new XAttribute("Type", bbControlRow.Type), - new XAttribute("X", bbControlRow.X), - new XAttribute("Y", bbControlRow.Y), - new XAttribute("Width", bbControlRow.Width), - new XAttribute("Height", bbControlRow.Height), - null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text)); - - if (null != bbControlRow[7]) - { - SetControlAttributes(bbControlRow.Attributes, xControl); - } - - if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard)) - { - xBillboard.Add(xControl); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); - } - } - } - - /// - /// Decompile the Billboard table. - /// - /// The table to decompile. - private void DecompileBillboardTable(Table table) - { - var billboards = new SortedList(); - - foreach (var row in table.Rows) - { - var xBillboard = new XElement(Names.BillboardElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Feature", row.FieldAsString(1))); - - this.IndexElement(row, xBillboard); - billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); - } - - var billboardActions = new Dictionary(); - - foreach (var row in billboards.Values) - { - var xBillboard = this.GetIndexedElement(row); - - if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction)) - { - xBillboardAction = new XElement(Names.BillboardActionElement, - new XAttribute("Id", row.FieldAsString(2))); - - this.UIElement.Add(xBillboardAction); - billboardActions.Add(row.FieldAsString(2), xBillboardAction); - } - - xBillboardAction.Add(xBillboard); - } - } - - /// - /// Decompile the Binary table. - /// - /// The table to decompile. - private void DecompileBinaryTable(Table table) - { - foreach (var row in table.Rows) - { - var xBinary = new XElement(Names.BinaryElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.RootElement.Add(xBinary); - } - } - - /// - /// Decompile the BindImage table. - /// - /// The table to decompile. - private void DecompileBindImageTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) - { - xFile.SetAttributeValue("BindPath", row.FieldAsString(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); - } - } - } - - /// - /// Decompile the Class table. - /// - /// The table to decompile. - private void DecompileClassTable(Table table) - { - foreach (var row in table.Rows) - { - var xClass = new XElement(Names.ClassElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Advertise", "yes"), - new XAttribute("Context", row.FieldAsString(1)), - row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)), - row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)), - row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)), - row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)), - row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)), - row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10))); - - if (!row.IsColumnNull(6)) - { - var fileTypeMaskStrings = row.FieldAsString(6).Split(';'); - - try - { - foreach (var fileTypeMaskString in fileTypeMaskStrings) - { - var fileTypeMaskParts = fileTypeMaskString.Split(','); - - if (4 == fileTypeMaskParts.Length) - { - var xFileTypeMask = new XElement(Names.FileTypeMaskElement, - new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)), - new XAttribute("Mask", fileTypeMaskParts[2]), - new XAttribute("Value", fileTypeMaskParts[3])); - - xClass.Add(xFileTypeMask); - } - else - { - // TODO: warn - } - } - } - catch (FormatException) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - } - catch (OverflowException) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - } - } - - if (!row.IsColumnNull(12)) - { - if (1 == row.FieldAsInteger(12)) - { - xClass.SetAttributeValue("RelativePath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); - } - } - - this.AddChildToParent("Component", xClass, row, 2); - this.IndexElement(row, xClass); - } - } - - /// - /// Decompile the ComboBox table. - /// - /// The table to decompile. - private void DecompileComboBoxTable(Table table) - { - // sort the combo boxes by their property and order - var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1))); - - XElement xComboBox = null; - string property = null; - foreach (var row in comboBoxRows) - { - if (null == xComboBox || row.FieldAsString(0) != property) - { - property = row.FieldAsString(0); - - xComboBox = new XElement(Names.ComboBoxElement, - new XAttribute("Property", property)); - - this.UIElement.Add(xComboBox); - } - - var xListItem = new XElement(Names.ListItemElement, - new XAttribute("Value", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); - xComboBox.Add(xListItem); - } - } - - /// - /// Decompile the Control table. - /// - /// The table to decompile. - private void DecompileControlTable(Table table) - { - foreach (ControlRow controlRow in table.Rows) - { - var xControl = new XElement(Names.ControlElement, - new XAttribute("Id", controlRow.Control), - new XAttribute("Type", controlRow.Type), - new XAttribute("X", controlRow.X), - new XAttribute("Y", controlRow.Y), - new XAttribute("Width", controlRow.Width), - new XAttribute("Height", controlRow.Height), - new XAttribute("Text", controlRow.Text)); - - if (!controlRow.IsColumnNull(7)) - { - string[] specialAttributes; - - // sets various common attributes like Disabled, Indirect, Integer, ... - SetControlAttributes(controlRow.Attributes, xControl); - - switch (controlRow.Type) - { - case "Bitmap": - specialAttributes = BitmapControlAttributes; - break; - case "CheckBox": - specialAttributes = CheckboxControlAttributes; - break; - case "ComboBox": - specialAttributes = ComboboxControlAttributes; - break; - case "DirectoryCombo": - specialAttributes = VolumeControlAttributes; - break; - case "Edit": - specialAttributes = EditControlAttributes; - break; - case "Icon": - specialAttributes = IconControlAttributes; - break; - case "ListBox": - specialAttributes = ListboxControlAttributes; - break; - case "ListView": - specialAttributes = ListviewControlAttributes; - break; - case "MaskedEdit": - specialAttributes = EditControlAttributes; - break; - case "PathEdit": - specialAttributes = EditControlAttributes; - break; - case "ProgressBar": - specialAttributes = ProgressControlAttributes; - break; - case "PushButton": - specialAttributes = ButtonControlAttributes; - break; - case "RadioButtonGroup": - specialAttributes = RadioControlAttributes; - break; - case "Text": - specialAttributes = TextControlAttributes; - break; - case "VolumeCostList": - specialAttributes = VolumeControlAttributes; - break; - case "VolumeSelectCombo": - specialAttributes = VolumeControlAttributes; - break; - default: - specialAttributes = null; - break; - } - - if (null != specialAttributes) - { - var iconSizeSet = false; - - for (var i = 16; 32 > i; i++) - { - if (1 == ((controlRow.Attributes >> i) & 1)) - { - string attribute = null; - - if (specialAttributes.Length > (i - 16)) - { - attribute = specialAttributes[i - 16]; - } - - // unknown attribute - if (null == attribute) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); - continue; - } - - switch (attribute) - { - case "Bitmap": - xControl.SetAttributeValue("Bitmap", "yes"); - break; - case "CDROM": - xControl.SetAttributeValue("CDROM", "yes"); - break; - case "ComboList": - xControl.SetAttributeValue("ComboList", "yes"); - break; - case "ElevationShield": - xControl.SetAttributeValue("ElevationShield", "yes"); - break; - case "Fixed": - xControl.SetAttributeValue("Fixed", "yes"); - break; - case "FixedSize": - xControl.SetAttributeValue("FixedSize", "yes"); - break; - case "Floppy": - xControl.SetAttributeValue("Floppy", "yes"); - break; - case "FormatSize": - xControl.SetAttributeValue("FormatSize", "yes"); - break; - case "HasBorder": - xControl.SetAttributeValue("HasBorder", "yes"); - break; - case "Icon": - xControl.SetAttributeValue("Icon", "yes"); - break; - case "Icon16": - if (iconSizeSet) - { - xControl.SetAttributeValue("IconSize", "48"); - } - else - { - iconSizeSet = true; - xControl.SetAttributeValue("IconSize", "16"); - } - break; - case "Icon32": - if (iconSizeSet) - { - xControl.SetAttributeValue("IconSize", "48"); - } - else - { - iconSizeSet = true; - xControl.SetAttributeValue("IconSize", "32"); - } - break; - case "Image": - xControl.SetAttributeValue("Image", "yes"); - break; - case "Multiline": - xControl.SetAttributeValue("Multiline", "yes"); - break; - case "NoPrefix": - xControl.SetAttributeValue("NoPrefix", "yes"); - break; - case "NoWrap": - xControl.SetAttributeValue("NoWrap", "yes"); - break; - case "Password": - xControl.SetAttributeValue("Password", "yes"); - break; - case "ProgressBlocks": - xControl.SetAttributeValue("ProgressBlocks", "yes"); - break; - case "PushLike": - xControl.SetAttributeValue("PushLike", "yes"); - break; - case "RAMDisk": - xControl.SetAttributeValue("RAMDisk", "yes"); - break; - case "Remote": - xControl.SetAttributeValue("Remote", "yes"); - break; - case "Removable": - xControl.SetAttributeValue("Removable", "yes"); - break; - case "ShowRollbackCost": - xControl.SetAttributeValue("ShowRollbackCost", "yes"); - break; - case "Sorted": - xControl.SetAttributeValue("Sorted", "yes"); - break; - case "Transparent": - xControl.SetAttributeValue("Transparent", "yes"); - break; - case "UserLanguage": - xControl.SetAttributeValue("UserLanguage", "yes"); - break; - default: - throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); - } - } - } - } - else if (0 < (controlRow.Attributes & 0xFFFF0000)) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); - } - } - - // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef - if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type)) - { - xControl.SetAttributeValue("Property", controlRow.Property); - } - - if (null != controlRow.Help) - { - var help = controlRow.Help.Split('|'); - - if (2 == help.Length) - { - if (0 < help[0].Length) - { - xControl.SetAttributeValue("ToolTip", help[0]); - } - - if (0 < help[1].Length) - { - xControl.SetAttributeValue("Help", help[1]); - } - } - } - - this.IndexElement(controlRow, xControl); - } - } - - /// - /// Decompile the ControlCondition table. - /// - /// The table to decompile. - private void DecompileControlConditionTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) - { - switch (row.FieldAsString(2)) - { - case "Default": - xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3)); - break; - case "Disable": - xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3)); - break; - case "Enable": - xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3)); - break; - case "Hide": - xControl.SetAttributeValue("HideCondition", row.FieldAsString(3)); - break; - case "Show": - xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3)); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); - } - } - } - - /// - /// Decompile the ControlEvent table. - /// - /// The table to decompile. - private void DecompileControlEventTable(Table table) - { - var controlEvents = new SortedList(); - - foreach (var row in table.Rows) - { - var xPublish = new XElement(Names.PublishElement, - new XAttribute("Condition", row.FieldAsString(4))); - - var publishEvent = row.FieldAsString(2); - if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) - { - xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2)); - - if ("{}" != row.FieldAsString(3)) - { - xPublish.SetAttributeValue("Value", row.FieldAsString(3)); - } - } - else - { - xPublish.SetAttributeValue("Event", publishEvent); - xPublish.SetAttributeValue("Value", row.FieldAsString(3)); - } - - controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row); - - this.IndexElement(row, xPublish); - } - - foreach (var row in controlEvents.Values) - { - if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) - { - var xPublish = this.GetIndexedElement(row); - xControl.Add(xPublish); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); - } - } - } - - /// - /// Decompile a custom table. - /// - /// The table to decompile. - private void DecompileCustomTable(Table table) - { - if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) - { - this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); - - var xCustomTable = new XElement(Names.CustomTableElement, - new XAttribute("Id", table.Name)); - - foreach (var columnDefinition in table.Definition.Columns) - { - var xColumn = new XElement(Names.ColumnElement, - new XAttribute("Id", columnDefinition.Name), - columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description), - columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable), - !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value), - !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"), - !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value), - !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value), - !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"), - !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"), - columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"), - new XAttribute("Width", columnDefinition.Length)); - - if (ColumnCategory.Unknown != columnDefinition.Category) - { - switch (columnDefinition.Category) - { - case ColumnCategory.Text: - xColumn.SetAttributeValue("Category", "text"); - break; - case ColumnCategory.UpperCase: - xColumn.SetAttributeValue("Category", "upperCase"); - break; - case ColumnCategory.LowerCase: - xColumn.SetAttributeValue("Category", "lowerCase"); - break; - case ColumnCategory.Integer: - xColumn.SetAttributeValue("Category", "integer"); - break; - case ColumnCategory.DoubleInteger: - xColumn.SetAttributeValue("Category", "doubleInteger"); - break; - case ColumnCategory.TimeDate: - xColumn.SetAttributeValue("Category", "timeDate"); - break; - case ColumnCategory.Identifier: - xColumn.SetAttributeValue("Category", "identifier"); - break; - case ColumnCategory.Property: - xColumn.SetAttributeValue("Category", "property"); - break; - case ColumnCategory.Filename: - xColumn.SetAttributeValue("Category", "filename"); - break; - case ColumnCategory.WildCardFilename: - xColumn.SetAttributeValue("Category", "wildCardFilename"); - break; - case ColumnCategory.Path: - xColumn.SetAttributeValue("Category", "path"); - break; - case ColumnCategory.Paths: - xColumn.SetAttributeValue("Category", "paths"); - break; - case ColumnCategory.AnyPath: - xColumn.SetAttributeValue("Category", "anyPath"); - break; - case ColumnCategory.DefaultDir: - xColumn.SetAttributeValue("Category", "defaultDir"); - break; - case ColumnCategory.RegPath: - xColumn.SetAttributeValue("Category", "regPath"); - break; - case ColumnCategory.Formatted: - xColumn.SetAttributeValue("Category", "formatted"); - break; - case ColumnCategory.FormattedSDDLText: - xColumn.SetAttributeValue("Category", "formattedSddl"); - break; - case ColumnCategory.Template: - xColumn.SetAttributeValue("Category", "template"); - break; - case ColumnCategory.Condition: - xColumn.SetAttributeValue("Category", "condition"); - break; - case ColumnCategory.Guid: - xColumn.SetAttributeValue("Category", "guid"); - break; - case ColumnCategory.Version: - xColumn.SetAttributeValue("Category", "version"); - break; - case ColumnCategory.Language: - xColumn.SetAttributeValue("Category", "language"); - break; - case ColumnCategory.Binary: - xColumn.SetAttributeValue("Category", "binary"); - break; - case ColumnCategory.CustomSource: - xColumn.SetAttributeValue("Category", "customSource"); - break; - case ColumnCategory.Cabinet: - xColumn.SetAttributeValue("Category", "cabinet"); - break; - case ColumnCategory.Shortcut: - xColumn.SetAttributeValue("Category", "shortcut"); - break; - default: - throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); - } - } - - if (ColumnModularizeType.None != columnDefinition.ModularizeType) - { - switch (columnDefinition.ModularizeType) - { - case ColumnModularizeType.Column: - xColumn.SetAttributeValue("Modularize", "Column"); - break; - case ColumnModularizeType.Condition: - xColumn.SetAttributeValue("Modularize", "Condition"); - break; - case ColumnModularizeType.Icon: - xColumn.SetAttributeValue("Modularize", "Icon"); - break; - case ColumnModularizeType.Property: - xColumn.SetAttributeValue("Modularize", "Property"); - break; - case ColumnModularizeType.SemicolonDelimited: - xColumn.SetAttributeValue("Modularize", "SemicolonDelimited"); - break; - default: - throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); - } - } - - if (ColumnType.Unknown != columnDefinition.Type) - { - switch (columnDefinition.Type) - { - case ColumnType.Localized: - xColumn.SetAttributeValue("Localizable", "yes"); - xColumn.SetAttributeValue("Type", "string"); - break; - case ColumnType.Number: - xColumn.SetAttributeValue("Type", "int"); - break; - case ColumnType.Object: - xColumn.SetAttributeValue("Type", "binary"); - break; - case ColumnType.Preserved: - case ColumnType.String: - xColumn.SetAttributeValue("Type", "string"); - break; - default: - throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'."); - } - } - - xCustomTable.Add(xColumn); - } - - foreach (var row in table.Rows) - { - var xRow = new XElement(Names.RowElement); - - foreach (var field in row.Fields.Where(f => f.Data != null)) - { - var xData = new XElement(Names.DataElement, - new XAttribute("Column", field.Column.Name), - new XAttribute("Value", field.AsString())); - - xRow.Add(xData); - } - - xCustomTable.Add(xRow); - } - - this.RootElement.Add(xCustomTable); - } - } - - /// - /// Decompile the CreateFolder table. - /// - /// The table to decompile. - private void DecompileCreateFolderTable(Table table) - { - foreach (var row in table.Rows) - { - var xCreateFolder = new XElement(Names.CreateFolderElement, - new XAttribute("Directory", row.FieldAsString(0))); - - this.AddChildToParent("Component", xCreateFolder, row, 1); - this.IndexElement(row, xCreateFolder); - } - } - - /// - /// Decompile the CustomAction table. - /// - /// The table to decompile. - private void DecompileCustomActionTable(Table table) - { - foreach (var row in table.Rows) - { - var xCustomAction = new XElement(Names.CustomActionElement, - new XAttribute("Id", row.FieldAsString(0))); - - var type = row.FieldAsInteger(1); - - if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)) - { - xCustomAction.SetAttributeValue("HideTarget", "yes"); - } - - if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate)) - { - xCustomAction.SetAttributeValue("Impersonate", "no"); - } - - if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware)) - { - xCustomAction.SetAttributeValue("TerminalServerAware", "yes"); - } - - if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) - { - xCustomAction.SetAttributeValue("Bitness", "always64"); - } - else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || - WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) - { - xCustomAction.SetAttributeValue("Bitness", "always32"); - } - - switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) - { - case 0: - // this is the default value - break; - case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: - xCustomAction.SetAttributeValue("Execute", "firstSequence"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: - xCustomAction.SetAttributeValue("Execute", "oncePerProcess"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: - xCustomAction.SetAttributeValue("Execute", "secondSequence"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript: - xCustomAction.SetAttributeValue("Execute", "deferred"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: - xCustomAction.SetAttributeValue("Execute", "rollback"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: - xCustomAction.SetAttributeValue("Execute", "commit"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits) - { - case 0: - // this is the default value - break; - case WindowsInstallerConstants.MsidbCustomActionTypeContinue: - xCustomAction.SetAttributeValue("Return", "ignore"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeAsync: - xCustomAction.SetAttributeValue("Return", "asyncWait"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: - xCustomAction.SetAttributeValue("Return", "asyncNoWait"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits; - switch (source) - { - case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: - if (!row.IsColumnNull(2)) - { - xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: - if (!row.IsColumnNull(2)) - { - xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeProperty: - xCustomAction.SetAttributeValue("Property", row.FieldAsString(2)); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits) - { - case WindowsInstallerConstants.MsidbCustomActionTypeDll: - xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe: - xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeTextData: - if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) - { - xCustomAction.SetAttributeValue("Error", row.FieldAsString(3)); - } - else - { - xCustomAction.SetAttributeValue("Value", row.FieldAsString(3)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeJScript: - if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) - { - xCustomAction.SetAttributeValue("Script", "jscript"); - // TODO: Extract to @ScriptFile? - // xCustomAction.Content = row.FieldAsString(3); - } - else - { - xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: - if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) - { - xCustomAction.SetAttributeValue("Script", "vbscript"); - // TODO: Extract to @ScriptFile? - // xCustomAction.Content = row.FieldAsString(3); - } - else - { - xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInstall: - this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - continue; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0; - if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall)) - { - xCustomAction.SetAttributeValue("PatchUninstall", "yes"); - } - - this.RootElement.Add(xCustomAction); - this.IndexElement(row, xCustomAction); - } - } - - /// - /// Decompile the CompLocator table. - /// - /// The table to decompile. - private void DecompileCompLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xComponentSearch = new XElement(Names.ComponentSearchElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Guid", row.FieldAsString(1))); - - if (!row.IsColumnNull(2)) - { - switch (row.FieldAsInteger(2)) - { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - xComponentSearch.SetAttributeValue("Type", "directory"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - // this is the default value - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - } - - this.IndexElement(row, xComponentSearch); - } - } - - /// - /// Decompile the Complus table. - /// - /// The table to decompile. - private void DecompileComplusTable(Table table) - { - foreach (var row in table.Rows) - { - if (!row.IsColumnNull(1)) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) - { - xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); - } - } - } - } - - /// - /// Decompile the Component table. - /// - /// The table to decompile. - private void DecompileComponentTable(Table table) - { - foreach (var row in table.Rows) - { - var xComponent = new XElement(Names.ComponentElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty)); - - var attributes = row.FieldAsInteger(3); - - if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)) - { - xComponent.SetAttributeValue("Location", "source"); - } - else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional)) - { - xComponent.SetAttributeValue("Location", "either"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount)) - { - xComponent.SetAttributeValue("SharedDllRefCount", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent)) - { - xComponent.SetAttributeValue("Permanent", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive)) - { - xComponent.SetAttributeValue("Transitive", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite)) - { - xComponent.SetAttributeValue("NeverOverwrite", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) - { - xComponent.SetAttributeValue("Bitness", "always64"); - } - else - { - xComponent.SetAttributeValue("Bitness", "always32"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) - { - xComponent.SetAttributeValue("DisableRegistryReflection", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence)) - { - xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared)) - { - xComponent.SetAttributeValue("Shared", "yes"); - } - - if (!row.IsColumnNull(4)) - { - xComponent.SetAttributeValue("Condition", row.FieldAsString(4)); - } - - this.AddChildToParent("Directory", xComponent, row, 2); - this.IndexElement(row, xComponent); - } - } - - /// - /// Decompile the Condition table. - /// - /// The table to decompile. - private void DecompileConditionTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0))) - { - var xLevel = new XElement(Names.LevelElement, - row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)), - new XAttribute("Level", row.FieldAsInteger(1))); - - xFeature.Add(xLevel); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature")); - } - } - } - - /// - /// Decompile the Dialog table. - /// - /// The table to decompile. - private void DecompileDialogTable(Table table) - { - foreach (var row in table.Rows) - { - var attributes = row.FieldAsNullableInteger(5) ?? 0; - - var xDialog = new XElement(Names.DialogElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("X", row.FieldAsString(1)), - new XAttribute("Y", row.FieldAsString(2)), - new XAttribute("Width", row.FieldAsString(3)), - new XAttribute("Height", row.FieldAsString(4)), - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null, - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null, - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null, - !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null); - - this.UIElement.Add(xDialog); - this.IndexElement(row, xDialog); - } - } - - /// - /// Decompile the Directory table. - /// - /// The table to decompile. - private void DecompileDirectoryTable(Table table) - { - foreach (var row in table.Rows) - { - var id = row.FieldAsString(0); - var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement; - var xDirectory = new XElement(elementName, - new XAttribute("Id", id)); - - if (!WindowsInstallerStandard.IsStandardDirectory(id)) - { - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - - if (id == "TARGETDIR" && names[0] != "SourceDir") - { - this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); - xDirectory.SetAttributeValue("Name", "SourceDir"); - } - else - { - if (null != names[0] && "." != names[0]) - { - if (null != names[1]) - { - xDirectory.SetAttributeValue("ShortName", names[0]); - } - else - { - xDirectory.SetAttributeValue("Name", names[0]); - } - } - - if (null != names[1]) - { - xDirectory.SetAttributeValue("Name", names[1]); - } - } - - if (null != names[2]) - { - if (null != names[3]) - { - xDirectory.SetAttributeValue("ShortSourceName", names[2]); - } - else - { - xDirectory.SetAttributeValue("SourceName", names[2]); - } - } - - if (null != names[3]) - { - xDirectory.SetAttributeValue("SourceName", names[3]); - } - } - - this.IndexElement(row, xDirectory); - } - - // nest the directories - foreach (var row in table.Rows) - { - var xDirectory = this.GetIndexedElement(row); - - var id = row.FieldAsString(0); - - if (id == "TARGETDIR") - { - // Skip TARGETDIR (but see below!). - } - else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) - { - this.RootElement.Add(xDirectory); - } - else - { - var parentDirectoryId = row.FieldAsString(1); - - if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); - } - else if (xParentDirectory == xDirectory) // another way to specify a root directory - { - this.RootElement.Add(xDirectory); - } - else - { - // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root. - if (parentDirectoryId == "TARGETDIR") - { - this.RootElement.Add(xDirectory); - } - else - { - xParentDirectory.Add(xDirectory); - } - } - } - } - } - - /// - /// Decompile the DrLocator table. - /// - /// The table to decompile. - private void DecompileDrLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xDirectorySearch = new XElement(Names.DirectorySearchElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Path", row, 2), - XAttributeIfNotNull("Depth", row, 3)); - - this.IndexElement(row, xDirectorySearch); - } - } - - /// - /// Decompile the DuplicateFile table. - /// - /// The table to decompile. - private void DecompileDuplicateFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xCopyFile = new XElement(Names.CopyFileElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("FileId", row.FieldAsString(2))); - - if (!row.IsColumnNull(3)) - { - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); - if (null != names[0] && null != names[1]) - { - xCopyFile.SetAttributeValue("DestinationShortName", names[0]); - xCopyFile.SetAttributeValue("DestinationName", names[1]); - } - else if (null != names[0]) - { - xCopyFile.SetAttributeValue("DestinationName", names[0]); - } - } - - // destination directory/property is set in FinalizeDuplicateMoveFileTables - - this.AddChildToParent("Component", xCopyFile, row, 1); - this.IndexElement(row, xCopyFile); - } - } - - /// - /// Decompile the Environment table. - /// - /// The table to decompile. - private void DecompileEnvironmentTable(Table table) - { - foreach (var row in table.Rows) - { - var xEnvironment = new XElement(Names.EnvironmentElement, - new XAttribute("Id", row.FieldAsString(0))); - - var done = false; - var permanent = true; - var name = row.FieldAsString(1); - for (var i = 0; i < name.Length && !done; i++) - { - switch (name[i]) - { - case '=': - xEnvironment.SetAttributeValue("Action", "set"); - break; - case '+': - xEnvironment.SetAttributeValue("Action", "create"); - break; - case '-': - permanent = false; - break; - case '!': - xEnvironment.SetAttributeValue("Action", "remove"); - break; - case '*': - xEnvironment.SetAttributeValue("System", "yes"); - break; - default: - xEnvironment.SetAttributeValue("Name", name.Substring(i)); - done = true; - break; - } - } - - if (permanent) - { - xEnvironment.SetAttributeValue("Permanent", "yes"); - } - - if (!row.IsColumnNull(2)) - { - var value = row.FieldAsString(2); - - if (value.StartsWith("[~]", StringComparison.Ordinal)) - { - xEnvironment.SetAttributeValue("Part", "last"); - - if (3 < value.Length) - { - xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1)); - xEnvironment.SetAttributeValue("Value", value.Substring(4)); - } - } - else if (value.EndsWith("[~]", StringComparison.Ordinal)) - { - xEnvironment.SetAttributeValue("Part", "first"); - - if (3 < value.Length) - { - xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1)); - xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4)); - } - } - else - { - xEnvironment.SetAttributeValue("Value", value); - } - } - - this.AddChildToParent("Component", xEnvironment, row, 3); - } - } - - /// - /// Decompile the Error table. - /// - /// The table to decompile. - private void DecompileErrorTable(Table table) - { - foreach (var row in table.Rows) - { - var xError = new XElement(Names.ErrorElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Message", row.FieldAsString(1))); - - this.UIElement.Add(xError); - } - } - - /// - /// Decompile the EventMapping table. - /// - /// The table to decompile. - private void DecompileEventMappingTable(Table table) - { - foreach (var row in table.Rows) - { - var xSubscribe = new XElement(Names.SubscribeElement, - new XAttribute("Event", row.FieldAsString(2)), - new XAttribute("Attribute", row.FieldAsString(3))); - - if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) - { - xControl.Add(xSubscribe); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); - } - } - } - - /// - /// Decompile the Extension table. - /// - /// The table to decompile. - private void DecompileExtensionTable(Table table) - { - foreach (var row in table.Rows) - { - var xExtension = new XElement(Names.ExtensionElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Advertise", "yes")); - - if (!row.IsColumnNull(3)) - { - if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3))) - { - xMime.SetAttributeValue("Default", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); - } - } - - if (!row.IsColumnNull(2)) - { - this.AddChildToParent("ProgId", xExtension, row, 2); - } - else - { - this.AddChildToParent("Component", xExtension, row, 1); - } - - this.IndexElement(row, xExtension); - } - } - - /// - /// Decompile the ExternalFiles table. - /// - /// The table to decompile. - private void DecompileExternalFilesTable(Table table) - { - foreach (var row in table.Rows) - { - var xExternalFile = new XElement(Names.ExternalFileElement, - new XAttribute("File", row.FieldAsString(1)), - new XAttribute("Source", row.FieldAsString(2))); - - AddSymbolPaths(row, 3, xExternalFile); - - if (!row.IsColumnNull(4) && !row.IsColumnNull(5)) - { - var ignoreOffsets = row.FieldAsString(4).Split(','); - var ignoreLengths = row.FieldAsString(5).Split(','); - - if (ignoreOffsets.Length == ignoreLengths.Length) - { - for (var i = 0; i < ignoreOffsets.Length; i++) - { - var xIgnoreRange = new XElement(Names.IgnoreRangeElement); - - if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); - } - - if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); - } - - xExternalFile.Add(xIgnoreRange); - } - } - else - { - // TODO: warn - } - } - else if (!row.IsColumnNull(4) || !row.IsColumnNull(5)) - { - // TODO: warn about mismatch between columns - } - - // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - - if (!row.IsColumnNull(7)) - { - xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7)); - } - - this.AddChildToParent("ImageFamilies", xExternalFile, row, 0); - this.IndexElement(row, xExternalFile); - } - } - - /// - /// Decompile the Feature table. - /// - /// The table to decompile. - private void DecompileFeatureTable(Table table) - { - var sortedFeatures = new SortedList(); - - foreach (var row in table.Rows) - { - var feature = new XElement(Names.FeatureElement, - new XAttribute("Id", row.FieldAsString(0)), - row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)), - new XAttribute("Level", row.FieldAsInteger(5)), - row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6))); - - if (row.IsColumnNull(4)) - { - feature.SetAttributeValue("Display", "hidden"); - } - else - { - var display = row.FieldAsInteger(4); - - if (0 == display) - { - feature.SetAttributeValue("Display", "hidden"); - } - else if (1 == display % 2) - { - feature.SetAttributeValue("Display", "expand"); - } - } - - var attributes = row.FieldAsInteger(7); - - if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) - { - // TODO: display a warning for setting favor local and follow parent together - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)) - { - feature.SetAttributeValue("InstallDefault", "source"); - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) - { - feature.SetAttributeValue("InstallDefault", "followParent"); - } - - if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise)) - { - feature.SetAttributeValue("InstallDefault", "advertise"); - } - - if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) && - WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) - { - this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); - feature.SetAttributeValue("AllowAdvertise", "no"); - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise)) - { - feature.SetAttributeValue("AllowAdvertise", "no"); - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) - { - feature.SetAttributeValue("AllowAdvertise", "system"); - } - - if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent)) - { - feature.SetAttributeValue("Absent", "disallow"); - } - - this.IndexElement(row, feature); - - // sort the features by their display column (and append the identifier to ensure unique keys) - sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row); - } - - // nest the features - foreach (var row in sortedFeatures.Values) - { - var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0)); - - if (row.IsColumnNull(1)) - { - this.RootElement.Add(xFeature); - } - else - { - if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1))) - { - if (xParentFeature == xFeature) - { - // TODO: display a warning about self-nesting - } - else - { - xParentFeature.Add(xFeature); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature")); - } - } - } - } - - /// - /// Decompile the FeatureComponents table. - /// - /// The table to decompile. - private void DecompileFeatureComponentsTable(Table table) - { - foreach (var row in table.Rows) - { - var xComponentRef = new XElement(Names.ComponentRefElement, - new XAttribute("Id", row.FieldAsString(1))); - - this.AddChildToParent("Feature", xComponentRef, row, 0); - this.IndexElement(row, xComponentRef); - } - } - - /// - /// Decompile the File table. - /// - /// The table to decompile. - private void DecompileFileTable(Table table) - { - foreach (FileRow fileRow in table.Rows) - { - var xFile = new XElement(Names.FileElement, - new XAttribute("Id", fileRow.File), - WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null, - null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null); - - var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName); - if (null != names[0] && null != names[1]) - { - xFile.SetAttributeValue("ShortName", names[0]); - xFile.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xFile.SetAttributeValue("Name", names[0]); - } - - if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) && - WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) - { - // TODO: error - } - else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed)) - { - xFile.SetAttributeValue("Compressed", "no"); - } - else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) - { - xFile.SetAttributeValue("Compressed", "yes"); - } - - this.IndexElement(fileRow, xFile); - } - } - - /// - /// Decompile the FileSFPCatalog table. - /// - /// The table to decompile. - private void DecompileFileSFPCatalogTable(Table table) - { - foreach (var row in table.Rows) - { - var xSfpFile = new XElement(Names.SFPFileElement, - new XAttribute("Id", row.FieldAsString(0))); - - this.AddChildToParent("SFPCatalog", xSfpFile, row, 1); - } - } - - /// - /// Decompile the Font table. - /// - /// The table to decompile. - private void DecompileFontTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) - { - if (!row.IsColumnNull(1)) - { - xFile.SetAttributeValue("FontTitle", row.FieldAsString(1)); - } - else - { - xFile.SetAttributeValue("TrueType", "yes"); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); - } - } - } - - /// - /// Decompile the Icon table. - /// - /// The table to decompile. - private void DecompileIconTable(Table table) - { - foreach (var row in table.Rows) - { - var icon = new XElement(Names.IconElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.RootElement.Add(icon); - } - } - - /// - /// Decompile the ImageFamilies table. - /// - /// The table to decompile. - private void DecompileImageFamiliesTable(Table table) - { - foreach (var row in table.Rows) - { - var family = new XElement(Names.FamilyElement, - new XAttribute("Name", row.FieldAsString(0)), - row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)), - row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)), - row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5))); - - this.RootElement.Add(family); - this.IndexElement(row, family); - } - } - - /// - /// Decompile the IniFile table. - /// - /// The table to decompile. - private void DecompileIniFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xIniFile = new XElement(Names.IniFileElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Section", row.FieldAsString(3)), - new XAttribute("Key", row.FieldAsString(4)), - new XAttribute("Value", row.FieldAsString(5)), - row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2))); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - - if (null != names[0]) - { - if (null == names[1]) - { - xIniFile.SetAttributeValue("Name", names[0]); - } - else - { - xIniFile.SetAttributeValue("ShortName", names[0]); - } - } - - if (null != names[1]) - { - xIniFile.SetAttributeValue("Name", names[1]); - } - - switch (row.FieldAsInteger(6)) - { - case WindowsInstallerConstants.MsidbIniFileActionAddLine: - xIniFile.SetAttributeValue("Action", "addLine"); - break; - case WindowsInstallerConstants.MsidbIniFileActionCreateLine: - xIniFile.SetAttributeValue("Action", "createLine"); - break; - case WindowsInstallerConstants.MsidbIniFileActionAddTag: - xIniFile.SetAttributeValue("Action", "addTag"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - this.AddChildToParent("Component", xIniFile, row, 7); - } - } - - /// - /// Decompile the IniLocator table. - /// - /// The table to decompile. - private void DecompileIniLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xIniFileSearch = new XElement(Names.IniFileSearchElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Section", row.FieldAsString(2)), - new XAttribute("Key", row.FieldAsString(3)), - row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4))); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - if (null != names[0] && null != names[1]) - { - xIniFileSearch.SetAttributeValue("ShortName", names[0]); - xIniFileSearch.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xIniFileSearch.SetAttributeValue("Name", names[0]); - } - - if (!row.IsColumnNull(5)) - { - switch (row.FieldAsInteger(5)) - { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - xIniFileSearch.SetAttributeValue("Type", "directory"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - // this is the default value - break; - case WindowsInstallerConstants.MsidbLocatorTypeRawValue: - xIniFileSearch.SetAttributeValue("Type", "raw"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - break; - } - } - - this.IndexElement(row, xIniFileSearch); - } - } - - /// - /// Decompile the IsolatedComponent table. - /// - /// The table to decompile. - private void DecompileIsolatedComponentTable(Table table) - { - foreach (var row in table.Rows) - { - var xIsolateComponent = new XElement(Names.IsolateComponentElement, - new XAttribute("Shared", row.FieldAsString(0))); - - this.AddChildToParent("Component", xIsolateComponent, row, 1); - } - } - - /// - /// Decompile the LaunchCondition table. - /// - /// The table to decompile. - private void DecompileLaunchConditionTable(Table table) - { - foreach (var row in table.Rows) - { - if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0)) - { - continue; // MajorUpgrade rows processed in FinalizeUpgradeTable - } - - var condition = new XElement(Names.LaunchElement, - new XAttribute("Condition", row.FieldAsString(0)), - new XAttribute("Message", row.FieldAsString(1))); - - this.RootElement.Add(condition); - } - } - - /// - /// Decompile the ListBox table. - /// - /// The table to decompile. - private void DecompileListBoxTable(Table table) - { - // sort the list boxes by their property and order - var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); - - XElement xListBox = null; - foreach (Row row in listBoxRows) - { - if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value) - { - xListBox = new XElement(Names.ListBoxElement, - new XAttribute("Property", row.FieldAsString(0))); - - this.UIElement.Add(xListBox); - } - - var listItem = new XElement(Names.ListItemElement, - new XAttribute("Value", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); - - xListBox.Add(listItem); - } - } - - /// - /// Decompile the ListView table. - /// - /// The table to decompile. - private void DecompileListViewTable(Table table) - { - // sort the list views by their property and order - var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); - - XElement xListView = null; - foreach (var row in listViewRows) - { - if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value) - { - xListView = new XElement(Names.ListViewElement, - new XAttribute("Property", row.FieldAsString(0))); - - this.UIElement.Add(xListView); - } - - var listItem = new XElement(Names.ListItemElement, - new XAttribute("Value", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)), - row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4))); - - xListView.Add(listItem); - } - } - - /// - /// Decompile the LockPermissions table. - /// - /// The table to decompile. - private void DecompileLockPermissionsTable(Table table) - { - foreach (var row in table.Rows) - { - var xPermission = new XElement(Names.PermissionElement, - row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)), - new XAttribute("User", row.FieldAsString(3))); - - string[] specialPermissions; - - switch (row.FieldAsString(1)) - { - case "CreateFolder": - specialPermissions = LockPermissionConstants.FolderPermissions; - break; - case "File": - specialPermissions = LockPermissionConstants.FilePermissions; - break; - case "Registry": - specialPermissions = LockPermissionConstants.RegistryPermissions; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; - } - - var permissionBits = row.FieldAsInteger(4); - for (var i = 0; i < 32; i++) - { - if (0 != ((permissionBits >> i) & 1)) - { - string name = null; - - if (specialPermissions.Length > i) - { - name = specialPermissions[i]; - } - else if (16 > i && specialPermissions.Length <= i) - { - name = "SpecificRightsAll"; - } - else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16)) - { - name = LockPermissionConstants.StandardPermissions[i - 16]; - } - else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28)) - { - name = LockPermissionConstants.GenericPermissions[i - 28]; - } - - if (null == name) - { - this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); - } - else - { - switch (name) - { - case "Append": - case "ChangePermission": - case "CreateChild": - case "CreateFile": - case "CreateLink": - case "CreateSubkeys": - case "Delete": - case "DeleteChild": - case "EnumerateSubkeys": - case "Execute": - case "FileAllRights": - case "GenericAll": - case "GenericExecute": - case "GenericRead": - case "GenericWrite": - case "Notify": - case "Read": - case "ReadAttributes": - case "ReadExtendedAttributes": - case "ReadPermission": - case "SpecificRightsAll": - case "Synchronize": - case "TakeOwnership": - case "Traverse": - case "Write": - case "WriteAttributes": - case "WriteExtendedAttributes": - xPermission.SetAttributeValue(name, "yes"); - break; - default: - throw new InvalidOperationException($"Unknown permission attribute '{name}'."); - } - } - } - } - - this.IndexElement(row, xPermission); - } - } - - /// - /// Decompile the Media table. - /// - /// The table to decompile. - private void DecompileMediaTable(Table table) - { - foreach (MediaRow mediaRow in table.Rows) - { - var xMedia = new XElement(Names.MediaElement, - new XAttribute("Id", mediaRow.DiskId), - mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt), - mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel)); - - if (null != mediaRow.Cabinet) - { - var cabinet = mediaRow.Cabinet; - - if (cabinet.StartsWith("#", StringComparison.Ordinal)) - { - xMedia.SetAttributeValue("EmbedCab", "yes"); - cabinet = cabinet.Substring(1); - } - - xMedia.SetAttributeValue("Cabinet", cabinet); - } - - this.RootElement.Add(xMedia); - this.IndexElement(mediaRow, xMedia); - } - } - - /// - /// Decompile the MIME table. - /// - /// The table to decompile. - private void DecompileMIMETable(Table table) - { - foreach (var row in table.Rows) - { - var mime = new XElement(Names.MIMEElement, - new XAttribute("ContentType", row.FieldAsString(0)), - row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2))); - - this.IndexElement(row, mime); - } - } - - /// - /// Decompile the ModuleConfiguration table. - /// - /// The table to decompile. - private void DecompileModuleConfigurationTable(Table table) - { - foreach (var row in table.Rows) - { - var configuration = new XElement(Names.ConfigurationElement, - new XAttribute("Name", row.FieldAsString(0)), - XAttributeIfNotNull("Type", row, 2), - XAttributeIfNotNull("ContextData", row, 3), - XAttributeIfNotNull("DefaultValue", row, 4), - XAttributeIfNotNull("DisplayName", row, 6), - XAttributeIfNotNull("Description", row, 7), - XAttributeIfNotNull("HelpLocation", row, 8), - XAttributeIfNotNull("HelpKeyword", row, 9)); - - switch (row.FieldAsInteger(1)) - { - case 0: - configuration.SetAttributeValue("Format", "Text"); - break; - case 1: - configuration.SetAttributeValue("Format", "Key"); - break; - case 2: - configuration.SetAttributeValue("Format", "Integer"); - break; - case 3: - configuration.SetAttributeValue("Format", "Bitfield"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - if (!row.IsColumnNull(5)) - { - var attributes = row.FieldAsInteger(5); - - if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan)) - { - configuration.SetAttributeValue("KeyNoOrphan", "yes"); - } - - if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable)) - { - configuration.SetAttributeValue("NonNullable", "yes"); - } - - if (3 < attributes) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - } - } - - this.RootElement.Add(configuration); - } - } - - /// - /// Decompile the ModuleDependency table. - /// - /// The table to decompile. - private void DecompileModuleDependencyTable(Table table) - { - foreach (var row in table.Rows) - { - var xDependency = new XElement(Names.DependencyElement, - new XAttribute("RequiredId", row.FieldAsString(2)), - new XAttribute("RequiredLanguage", row.FieldAsString(3)), - XAttributeIfNotNull("RequiredVersion", row, 4)); - - this.RootElement.Add(xDependency); - } - } - - /// - /// Decompile the ModuleExclusion table. - /// - /// The table to decompile. - private void DecompileModuleExclusionTable(Table table) - { - foreach (var row in table.Rows) - { - var xExclusion = new XElement(Names.ExclusionElement, - new XAttribute("ExcludedId", row.FieldAsString(2)), - XAttributeIfNotNull("ExcludedMinVersion", row, 4), - XAttributeIfNotNull("ExcludedMaxVersion", row, 5)); - - var excludedLanguage = row.FieldAsInteger(3); - if (0 < excludedLanguage) - { - xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage); - } - else if (0 > excludedLanguage) - { - xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage); - } - - this.RootElement.Add(xExclusion); - } - } - - /// - /// Decompile the ModuleIgnoreTable table. - /// - /// The table to decompile. - private void DecompileModuleIgnoreTableTable(Table table) - { - foreach (var row in table.Rows) - { - var tableName = row.FieldAsString(0); - - // the linker automatically adds a ModuleIgnoreTable row for some tables - if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) - { - var xIgnoreTable = new XElement(Names.IgnoreTableElement, - new XAttribute("Id", tableName)); - - this.RootElement.Add(xIgnoreTable); - } - } - } - - /// - /// Decompile the ModuleSignature table. - /// - /// The table to decompile. - private void DecompileModuleSignatureTable(Table table) - { - if (1 == table.Rows.Count) - { - var row = table.Rows[0]; - - this.RootElement.SetAttributeValue("Id", row.FieldAsString(0)); - // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) - this.RootElement.SetAttributeValue("Language", row.FieldAsString(1)); - this.RootElement.SetAttributeValue("Version", row.FieldAsString(2)); - } - else - { - // TODO: warn - } - } - - /// - /// Decompile the ModuleSubstitution table. - /// - /// The table to decompile. - private void DecompileModuleSubstitutionTable(Table table) - { - foreach (var row in table.Rows) - { - var xSubstitution = new XElement(Names.SubstitutionElement, - new XAttribute("Table", row.FieldAsString(0)), - new XAttribute("Row", row.FieldAsString(1)), - new XAttribute("Column", row.FieldAsString(2)), - XAttributeIfNotNull("Value", row, 3)); - - this.RootElement.Add(xSubstitution); - } - } - - /// - /// Decompile the MoveFile table. - /// - /// The table to decompile. - private void DecompileMoveFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xCopyFile = new XElement(Names.CopyFileElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("SourceName", row, 2)); - - if (!row.IsColumnNull(3)) - { - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); - if (null != names[0] && null != names[1]) - { - xCopyFile.SetAttributeValue("DestinationShortName", names[0]); - xCopyFile.SetAttributeValue("DestinationName", names[1]); - } - else if (null != names[0]) - { - xCopyFile.SetAttributeValue("DestinationName", names[0]); - } - } - - // source/destination directory/property is set in FinalizeDuplicateMoveFileTables - - switch (row.FieldAsInteger(6)) - { - case 0: - break; - case WindowsInstallerConstants.MsidbMoveFileOptionsMove: - xCopyFile.SetAttributeValue("Delete", "yes"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - this.AddChildToParent("Component", xCopyFile, row, 1); - this.IndexElement(row, xCopyFile); - } - } - - /// - /// Decompile the MsiDigitalCertificate table. - /// - /// The table to decompile. - private void DecompileMsiDigitalCertificateTable(Table table) - { - foreach (var row in table.Rows) - { - var xDigitalCertificate = new XElement(Names.DigitalCertificateElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.IndexElement(row, xDigitalCertificate); - } - } - - /// - /// Decompile the MsiDigitalSignature table. - /// - /// The table to decompile. - private void DecompileMsiDigitalSignatureTable(Table table) - { - foreach (var row in table.Rows) - { - var xDigitalSignature = new XElement(Names.DigitalSignatureElement, - XAttributeIfNotNull("SourceFile", row, 3)); - - this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2); - - if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1))) - { - xParentElement.Add(xDigitalSignature); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0))); - } - } - } - - /// - /// Decompile the MsiEmbeddedChainer table. - /// - /// The table to decompile. - private void DecompileMsiEmbeddedChainerTable(Table table) - { - foreach (var row in table.Rows) - { - var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Condition", row.FieldAsString(1)), - XAttributeIfNotNull("CommandLine", row, 2)); - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: - xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: - xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3)); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.RootElement.Add(xEmbeddedChainer); - } - } - - /// - /// Decompile the MsiEmbeddedUI table. - /// - /// The table to decompile. - private void DecompileMsiEmbeddedUITable(Table table) - { - var xEmbeddedUI = new XElement(Names.EmbeddedUIElement); - - var foundEmbeddedUI = false; - var foundEmbeddedResources = false; - - foreach (var row in table.Rows) - { - var attributes = row.FieldAsInteger(2); - - if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI)) - { - if (foundEmbeddedUI) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - } - else - { - xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0)); - xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1)); - - var messageFilter = row.FieldAsInteger(3); - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT)) - { - xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR)) - { - xEmbeddedUI.SetAttributeValue("IgnoreError", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING)) - { - xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER)) - { - xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART)) - { - xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA)) - { - xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS)) - { - xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA)) - { - xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG)) - { - xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes"); - } - - if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic)) - { - xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes"); - } - - xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4)); - - this.UIElement.Add(xEmbeddedUI); - foundEmbeddedUI = true; - } - } - else - { - var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(1)), - new XAttribute("SourceFile", row.FieldAsString(4))); - - xEmbeddedUI.Add(xEmbeddedResource); - foundEmbeddedResources = true; - } - } - - if (!foundEmbeddedUI && foundEmbeddedResources) - { - // TODO: warn - } - } - - /// - /// Decompile the MsiLockPermissionsEx table. - /// - /// The table to decompile. - private void DecompileMsiLockPermissionsExTable(Table table) - { - foreach (var row in table.Rows) - { - var xPermissionEx = new XElement(Names.PermissionExElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Sddl", row.FieldAsString(3)), - XAttributeIfNotNull("Condition", row, 4)); - - switch (row.FieldAsString(2)) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; - } - - this.IndexElement(row, xPermissionEx); - } - } - - /// - /// Decompile the MsiPackageCertificate table. - /// - /// The table to decompile. - private void DecompileMsiPackageCertificateTable(Table table) - { - if (0 < table.Rows.Count) - { - var xPackageCertificates = new XElement(Names.PatchCertificatesElement); - this.RootElement.Add(xPackageCertificates); - this.AddCertificates(table, xPackageCertificates); - } - } - - /// - /// Decompile the MsiPatchCertificate table. - /// - /// The table to decompile. - private void DecompileMsiPatchCertificateTable(Table table) - { - if (0 < table.Rows.Count) - { - var xPatchCertificates = new XElement(Names.PatchCertificatesElement); - this.RootElement.Add(xPatchCertificates); - this.AddCertificates(table, xPatchCertificates); - } - } - - /// - /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. - /// - /// The table being decompiled. - /// DigitalCertificate parent - private void AddCertificates(Table table, XElement parent) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1))) - { - parent.Add(xDigitalCertificate); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate")); - } - } - } - - /// - /// Decompile the MsiShortcutProperty table. - /// - /// The table to decompile. - private void DecompileMsiShortcutPropertyTable(Table table) - { - foreach (var row in table.Rows) - { - var xProperty = new XElement(Names.ShortcutPropertyElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - new XAttribute("Value", row.FieldAsString(3))); - - this.AddChildToParent("Shortcut", xProperty, row, 1); - } - } - - /// - /// Decompile the ODBCAttribute table. - /// - /// The table to decompile. - private void DecompileODBCAttributeTable(Table table) - { - foreach (var row in table.Rows) - { - var xProperty = new XElement(Names.PropertyElement, - new XAttribute("Id", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2))); - - this.AddChildToParent("ODBCDriver", xProperty, row, 0); - } - } - - /// - /// Decompile the ODBCDataSource table. - /// - /// The table to decompile. - private void DecompileODBCDataSourceTable(Table table) - { - foreach (var row in table.Rows) - { - var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(2)), - new XAttribute("DriverName", row.FieldAsString(3))); - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: - xOdbcDataSource.SetAttributeValue("Registration", "machine"); - break; - case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: - xOdbcDataSource.SetAttributeValue("Registration", "user"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.IndexElement(row, xOdbcDataSource); - } - } - - /// - /// Decompile the ODBCDriver table. - /// - /// The table to decompile. - private void DecompileODBCDriverTable(Table table) - { - foreach (var row in table.Rows) - { - var xOdbcDriver = new XElement(Names.ODBCDriverElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(2)), - new XAttribute("File", row.FieldAsString(3)), - XAttributeIfNotNull("SetupFile", row, 4)); - - this.AddChildToParent("Component", xOdbcDriver, row, 1); - this.IndexElement(row, xOdbcDriver); - } - } - - /// - /// Decompile the ODBCSourceAttribute table. - /// - /// The table to decompile. - private void DecompileODBCSourceAttributeTable(Table table) - { - foreach (var row in table.Rows) - { - var xProperty = new XElement(Names.PropertyElement, - new XAttribute("Id", row.FieldAsString(1)), - XAttributeIfNotNull("Value", row, 2)); - - this.AddChildToParent("ODBCDataSource", xProperty, row, 0); - } - } - - /// - /// Decompile the ODBCTranslator table. - /// - /// The table to decompile. - private void DecompileODBCTranslatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(2)), - new XAttribute("File", row.FieldAsString(3)), - XAttributeIfNotNull("SetupFile", row, 4)); - - this.AddChildToParent("Component", xOdbcTranslator, row, 1); - } - } - - /// - /// Decompile the PatchMetadata table. - /// - /// The table to decompile. - private void DecompilePatchMetadataTable(Table table) - { - if (0 < table.Rows.Count) - { - var xPatchMetadata = new XElement(Names.PatchMetadataElement); - - foreach (var row in table.Rows) - { - var value = row.FieldAsString(2); - - switch (row.FieldAsString(1)) - { - case "AllowRemoval": - if ("1" == value) - { - xPatchMetadata.SetAttributeValue("AllowRemoval", "yes"); - } - break; - case "Classification": - if (null != value) - { - xPatchMetadata.SetAttributeValue("Classification", value); - } - break; - case "CreationTimeUTC": - if (null != value) - { - xPatchMetadata.SetAttributeValue("CreationTimeUTC", value); - } - break; - case "Description": - if (null != value) - { - xPatchMetadata.SetAttributeValue("Description", value); - } - break; - case "DisplayName": - if (null != value) - { - xPatchMetadata.SetAttributeValue("DisplayName", value); - } - break; - case "ManufacturerName": - if (null != value) - { - xPatchMetadata.SetAttributeValue("ManufacturerName", value); - } - break; - case "MinorUpdateTargetRTM": - if (null != value) - { - xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value); - } - break; - case "MoreInfoURL": - if (null != value) - { - xPatchMetadata.SetAttributeValue("MoreInfoURL", value); - } - break; - case "OptimizeCA": - var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement); - var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); - if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA)) - { - xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes"); - } - - if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA)) - { - xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes"); - } - - if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA)) - { - xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes"); - } - - xPatchMetadata.Add(xOptimizeCustomActions); - break; - case "OptimizedInstallMode": - if ("1" == value) - { - xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes"); - } - break; - case "TargetProductName": - if (null != value) - { - xPatchMetadata.SetAttributeValue("TargetProductName", value); - } - break; - default: - var xCustomProperty = new XElement(Names.CustomPropertyElement, - XAttributeIfNotNull("Company", row, 0), - XAttributeIfNotNull("Property", row, 1), - XAttributeIfNotNull("Value", row, 2)); - - xPatchMetadata.Add(xCustomProperty); - break; - } - } - - this.RootElement.Add(xPatchMetadata); - } - } - - /// - /// Decompile the PatchSequence table. - /// - /// The table to decompile. - private void DecompilePatchSequenceTable(Table table) - { - foreach (var row in table.Rows) - { - var patchSequence = new XElement(Names.PatchSequenceElement, - new XAttribute("PatchFamily", row.FieldAsString(0))); - - if (!row.IsColumnNull(1)) - { - try - { - var guid = new Guid(row.FieldAsString(1)); - - patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1)); - } - catch // non-guid value - { - patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1)); - } - } - - if (!row.IsColumnNull(2)) - { - patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2)); - } - - if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3)) - { - patchSequence.SetAttributeValue("Supersede", "yes"); - } - - this.RootElement.Add(patchSequence); - } - } - - /// - /// Decompile the ProgId table. - /// - /// The table to decompile. - private void DecompileProgIdTable(Table table) - { - foreach (var row in table.Rows) - { - var xProgId = new XElement(Names.ProgIdElement, - new XAttribute("Advertise", "yes"), - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Description", row, 3), - XAttributeIfNotNull("Icon", row, 4), - XAttributeIfNotNull("IconIndex", row, 5)); - - this.IndexElement(row, xProgId); - } - - // nest the ProgIds - foreach (var row in table.Rows) - { - var xProgId = this.GetIndexedElement(row); - - if (!row.IsColumnNull(1)) - { - this.AddChildToParent("ProgId", xProgId, row, 1); - } - else if (!row.IsColumnNull(2)) - { - // nesting is handled in FinalizeProgIdTable - } - else - { - // TODO: warn for orphaned ProgId - } - } - } - - /// - /// Decompile the Properties table. - /// - /// The table to decompile. - private void DecompilePropertiesTable(Table table) - { - foreach (var row in table.Rows) - { - var name = row.FieldAsString(0); - var value = row.FieldAsString(1); - - switch (name) - { - case "AllowProductCodeMismatches": - if ("1" == value) - { - this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes"); - } - break; - case "AllowProductVersionMajorMismatches": - if ("1" == value) - { - this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes"); - } - break; - case "ApiPatchingSymbolFlags": - if (null != value) - { - try - { - // remove the leading "0x" if its present - if (value.StartsWith("0x", StringComparison.Ordinal)) - { - value = value.Substring(2); - } - - this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16)); - } - catch - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - } - } - break; - case "DontRemoveTempFolderWhenFinished": - if ("1" == value) - { - this.RootElement.SetAttributeValue("CleanWorkingFolder", "no"); - } - break; - case "IncludeWholeFilesOnly": - if ("1" == value) - { - this.RootElement.SetAttributeValue("WholeFilesOnly", "yes"); - } - break; - case "ListOfPatchGUIDsToReplace": - if (null != value) - { - var guidRegex = 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}\}"); - var guidMatches = guidRegex.Matches(value); - - foreach (Match guidMatch in guidMatches) - { - var xReplacePatch = new XElement(Names.ReplacePatchElement, - new XAttribute("Id", guidMatch.Value)); - - this.RootElement.Add(xReplacePatch); - } - } - break; - case "ListOfTargetProductCodes": - if (null != value) - { - var targetProductCodes = value.Split(';'); - - foreach (var targetProductCodeString in targetProductCodes) - { - var xTargetProductCode = new XElement(Names.TargetProductCodeElement, - new XAttribute("Id", targetProductCodeString)); - - this.RootElement.Add(xTargetProductCode); - } - } - break; - case "PatchGUID": - this.RootElement.SetAttributeValue("Id", value); - break; - case "PatchSourceList": - this.RootElement.SetAttributeValue("SourceList", value); - break; - case "PatchOutputPath": - this.RootElement.SetAttributeValue("OutputPath", value); - break; - default: - var patchProperty = new XElement(Names.PatchPropertyElement, - new XAttribute("Name", name), - new XAttribute("Value", value)); - - this.RootElement.Add(patchProperty); - break; - } - } - } - - /// - /// Decompile the Property table. - /// - /// The table to decompile. - private void DecompilePropertyTable(Table table) - { - foreach (var row in table.Rows) - { - var id = row.FieldAsString(0); - var value = row.FieldAsString(1); - - if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) - { - if (0 < value.Length) - { - foreach (var propertyId in value.Split(';')) - { - if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId) - { - continue; - } - - var property = propertyId; - var suppressModulularization = false; - if (OutputType.Module == this.OutputType) - { - if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) - { - property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1); - } - else - { - suppressModulularization = true; - } - } - - var xSpecialProperty = this.EnsureProperty(property); - if (suppressModulularization) - { - xSpecialProperty.SetAttributeValue("SuppressModularization", "yes"); - } - - switch (id) - { - case "AdminProperties": - xSpecialProperty.SetAttributeValue("Admin", "yes"); - break; - case "MsiHiddenProperties": - xSpecialProperty.SetAttributeValue("Hidden", "yes"); - break; - case "SecureCustomProperties": - xSpecialProperty.SetAttributeValue("Secure", "yes"); - break; - } - } - } - - continue; - } - else if (OutputType.Product == this.OutputType) - { - switch (id) - { - case "Manufacturer": - this.RootElement.SetAttributeValue("Manufacturer", value); - continue; - case "ProductCode": - this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture)); - continue; - case "ProductLanguage": - this.RootElement.SetAttributeValue("Language", value); - continue; - case "ProductName": - this.RootElement.SetAttributeValue("Name", value); - continue; - case "ProductVersion": - this.RootElement.SetAttributeValue("Version", value); - continue; - case "UpgradeCode": - this.RootElement.SetAttributeValue("UpgradeCode", value); - continue; - } - } - - if (!this.SuppressUI || "ErrorDialog" != id) - { - var xProperty = this.EnsureProperty(id); - - xProperty.SetAttributeValue("Value", value); - } - } - } - - /// - /// Decompile the PublishComponent table. - /// - /// The table to decompile. - private void DecompilePublishComponentTable(Table table) - { - foreach (var row in table.Rows) - { - var category = new XElement(Names.CategoryElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Qualifier", row.FieldAsString(1)), - XAttributeIfNotNull("AppData", row, 3)); - - this.AddChildToParent("Component", category, row, 2); - } - } - - /// - /// Decompile the RadioButton table. - /// - /// The table to decompile. - private void DecompileRadioButtonTable(Table table) - { - foreach (var row in table.Rows) - { - var radioButton = new XElement(Names.RadioButtonElement, - new XAttribute("Value", row.FieldAsString(2)), - new XAttribute("X", row.FieldAsInteger(3)), - new XAttribute("Y", row.FieldAsInteger(4)), - new XAttribute("Width", row.FieldAsInteger(5)), - new XAttribute("Height", row.FieldAsInteger(6)), - XAttributeIfNotNull("Text", row, 7)); - - if (!row.IsColumnNull(8)) - { - var help = (row.FieldAsString(8)).Split('|'); - - if (2 == help.Length) - { - if (0 < help[0].Length) - { - radioButton.SetAttributeValue("ToolTip", help[0]); - } - - if (0 < help[1].Length) - { - radioButton.SetAttributeValue("Help", help[1]); - } - } - } - - this.IndexElement(row, radioButton); - } - - // nest the radio buttons - var xRadioButtonGroups = new Dictionary(); - foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1))) - { - var xRadioButton = this.GetIndexedElement(row); - - if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup)) - { - xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement, - new XAttribute("Property", row.FieldAsString(0))); - - this.UIElement.Add(xRadioButtonGroup); - xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup); - } - - xRadioButtonGroup.Add(xRadioButton); - } - } - - /// - /// Decompile the Registry table. - /// - /// The table to decompile. - private void DecompileRegistryTable(Table table) - { - foreach (var row in table.Rows) - { - if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4)) - { - var xRegistryKey = new XElement(Names.RegistryKeyElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2))); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRegistryKey.SetAttributeValue("Root", registryRootType); - } - - switch (row.FieldAsString(3)) - { - case "+": - xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); - break; - case "-": - xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); - break; - case "*": - xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); - xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); - break; - } - - this.IndexElement(row, xRegistryKey); - } - else - { - var xRegistryValue = new XElement(Names.RegistryValueElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - XAttributeIfNotNull("Name", row, 3)); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRegistryValue.SetAttributeValue("Root", registryRootType); - } - - if (!row.IsColumnNull(4)) - { - var value = row.FieldAsString(4); - - if (value.StartsWith("#x", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "binary"); - xRegistryValue.SetAttributeValue("Value", value.Substring(2)); - } - else if (value.StartsWith("#%", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "expandable"); - xRegistryValue.SetAttributeValue("Value", value.Substring(2)); - } - else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "integer"); - xRegistryValue.SetAttributeValue("Value", value.Substring(1)); - } - else - { - if (value.StartsWith("##", StringComparison.Ordinal)) - { - value = value.Substring(1); - } - - if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "multiString"); - - if ("[~]" == value) - { - value = String.Empty; - } - else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) - { - value = value.Substring(3, value.Length - 6); - } - else if (value.StartsWith("[~]", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Action", "append"); - value = value.Substring(3); - } - else if (value.EndsWith("[~]", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Action", "prepend"); - value = value.Substring(0, value.Length - 3); - } - - var multiValues = NullSplitter.Split(value); - foreach (var multiValue in multiValues) - { - var xMultiStringValue = new XElement(Names.MultiStringElement, - new XAttribute("Value", multiValue)); - - xRegistryValue.Add(xMultiStringValue); - } - } - else - { - xRegistryValue.SetAttributeValue("Type", "string"); - xRegistryValue.SetAttributeValue("Value", value); - } - } - } - else - { - xRegistryValue.SetAttributeValue("Type", "string"); - xRegistryValue.SetAttributeValue("Value", String.Empty); - } - - this.IndexElement(row, xRegistryValue); - } - } - } - - /// - /// Decompile the RegLocator table. - /// - /// The table to decompile. - private void DecompileRegLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xRegistrySearch = new XElement(Names.RegistrySearchElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - XAttributeIfNotNull("Name", row, 3)); - - switch (row.FieldAsInteger(1)) - { - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - xRegistrySearch.SetAttributeValue("Root", "HKCR"); - break; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - xRegistrySearch.SetAttributeValue("Root", "HKCU"); - break; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - xRegistrySearch.SetAttributeValue("Root", "HKLM"); - break; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - xRegistrySearch.SetAttributeValue("Root", "HKU"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - if (row.IsColumnNull(4)) - { - xRegistrySearch.SetAttributeValue("Type", "file"); - } - else - { - var type = row.FieldAsInteger(4); - - if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) - { - xRegistrySearch.SetAttributeValue("Bitness", "always64"); - type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; - } - else - { - xRegistrySearch.SetAttributeValue("Bitness", "always32"); - } - - switch (type) - { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - xRegistrySearch.SetAttributeValue("Type", "directory"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - xRegistrySearch.SetAttributeValue("Type", "file"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeRawValue: - xRegistrySearch.SetAttributeValue("Type", "raw"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - } - - this.IndexElement(row, xRegistrySearch); - } - } - - /// - /// Decompile the RemoveFile table. - /// - /// The table to decompile. - private void DecompileRemoveFileTable(Table table) - { - foreach (var row in table.Rows) - { - if (row.IsColumnNull(2)) - { - var xRemoveFolder = new XElement(Names.RemoveFolderElement, - new XAttribute("Id", row.FieldAsString(0))); - - // directory/property is set in FinalizeDecompile - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: - xRemoveFolder.SetAttributeValue("On", "install"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: - xRemoveFolder.SetAttributeValue("On", "uninstall"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: - xRemoveFolder.SetAttributeValue("On", "both"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.AddChildToParent("Component", xRemoveFolder, row, 1); - this.IndexElement(row, xRemoveFolder); - } - else - { - var xRemoveFile = new XElement(Names.RemoveFileElement, - new XAttribute("Id", row.FieldAsString(0))); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - if (null != names[0] && null != names[1]) - { - xRemoveFile.SetAttributeValue("ShortName", names[0]); - xRemoveFile.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xRemoveFile.SetAttributeValue("Name", names[0]); - } - - // directory/property is set in FinalizeDecompile - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: - xRemoveFile.SetAttributeValue("On", "install"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: - xRemoveFile.SetAttributeValue("On", "uninstall"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: - xRemoveFile.SetAttributeValue("On", "both"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.AddChildToParent("Component", xRemoveFile, row, 1); - this.IndexElement(row, xRemoveFile); - } - } - } - - /// - /// Decompile the RemoveIniFile table. - /// - /// The table to decompile. - private void DecompileRemoveIniFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xIniFile = new XElement(Names.IniFileElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Directory", row, 2), - new XAttribute("Section", row.FieldAsString(3)), - new XAttribute("Key", row.FieldAsString(4)), - XAttributeIfNotNull("Value", row, 5)); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - if (null != names[0] && null != names[1]) - { - xIniFile.SetAttributeValue("ShortName", names[0]); - xIniFile.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xIniFile.SetAttributeValue("Name", names[0]); - } - - switch (row.FieldAsInteger(6)) - { - case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: - xIniFile.SetAttributeValue("Action", "removeLine"); - break; - case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: - xIniFile.SetAttributeValue("Action", "removeTag"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - this.AddChildToParent("Component", xIniFile, row, 7); - } - } - - /// - /// Decompile the RemoveRegistry table. - /// - /// The table to decompile. - private void DecompileRemoveRegistryTable(Table table) - { - foreach (var row in table.Rows) - { - if ("-" == row.FieldAsString(3)) - { - var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - new XAttribute("Action", "removeOnInstall")); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRemoveRegistryKey.SetAttributeValue("Root", registryRootType); - } - - this.AddChildToParent("Component", xRemoveRegistryKey, row, 4); - } - else - { - var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - XAttributeIfNotNull("Name", row, 3)); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRemoveRegistryValue.SetAttributeValue("Root", registryRootType); - } - - this.AddChildToParent("Component", xRemoveRegistryValue, row, 4); - } - } - } - - /// - /// Decompile the ReserveCost table. - /// - /// The table to decompile. - private void DecompileReserveCostTable(Table table) - { - foreach (var row in table.Rows) - { - var xReserveCost = new XElement(Names.ReserveCostElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Directory", row, 2), - new XAttribute("RunLocal", row.FieldAsString(3)), - new XAttribute("RunFromSource", row.FieldAsString(4))); - - this.AddChildToParent("Component", xReserveCost, row, 4); - } - } - - /// - /// Decompile the SelfReg table. - /// - /// The table to decompile. - private void DecompileSelfRegTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) - { - xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); - } - } - } - - /// - /// Decompile the ServiceControl table. - /// - /// The table to decompile. - private void DecompileServiceControlTable(Table table) - { - foreach (var row in table.Rows) - { - var xServiceControl = new XElement(Names.ServiceControlElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(1))); - - var eventValue = row.FieldAsInteger(2); - if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) && - WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) - { - xServiceControl.SetAttributeValue("Start", "both"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart)) - { - xServiceControl.SetAttributeValue("Start", "install"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) - { - xServiceControl.SetAttributeValue("Start", "uninstall"); - } - - if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) && - WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) - { - xServiceControl.SetAttributeValue("Stop", "both"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop)) - { - xServiceControl.SetAttributeValue("Stop", "install"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) - { - xServiceControl.SetAttributeValue("Stop", "uninstall"); - } - - if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) && - WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) - { - xServiceControl.SetAttributeValue("Remove", "both"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete)) - { - xServiceControl.SetAttributeValue("Remove", "install"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) - { - xServiceControl.SetAttributeValue("Remove", "uninstall"); - } - - if (!row.IsColumnNull(3)) - { - var arguments = NullSplitter.Split(row.FieldAsString(3)); - - foreach (var argument in arguments) - { - var xServiceArgument = new XElement(Names.ServiceArgumentElement, - new XAttribute("Value", argument)); - - xServiceControl.Add(xServiceArgument); - } - } - - if (!row.IsColumnNull(4)) - { - xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes"); - } - - this.AddChildToParent("Component", xServiceControl, row, 5); - } - } - - /// - /// Decompile the ServiceInstall table. - /// - /// The table to decompile. - private void DecompileServiceInstallTable(Table table) - { - foreach (var row in table.Rows) - { - var xServiceInstall = new XElement(Names.ServiceInstallElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(1)), - XAttributeIfNotNull("DisplayName", row, 2), - XAttributeIfNotNull("LoadOrderGroup", row, 6), - XAttributeIfNotNull("Account", row, 8), - XAttributeIfNotNull("Password", row, 9), - XAttributeIfNotNull("Arguments", row, 10), - XAttributeIfNotNull("Description", row, 12)); - - var serviceType = row.FieldAsInteger(3); - if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive)) - { - xServiceInstall.SetAttributeValue("Interactive", "yes"); - } - - if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) && - WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) - { - // TODO: warn - } - else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess)) - { - xServiceInstall.SetAttributeValue("Type", "ownProcess"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) - { - xServiceInstall.SetAttributeValue("Type", "shareProcess"); - } - - var startType = row.FieldAsInteger(4); - if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType) - { - xServiceInstall.SetAttributeValue("Start", "disabled"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType) - { - xServiceInstall.SetAttributeValue("Start", "demand"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType) - { - xServiceInstall.SetAttributeValue("Start", "auto"); - } - else - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - } - - var errorControl = row.FieldAsInteger(5); - if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical)) - { - xServiceInstall.SetAttributeValue("ErrorControl", "critical"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal)) - { - xServiceInstall.SetAttributeValue("ErrorControl", "normal"); - } - else - { - xServiceInstall.SetAttributeValue("ErrorControl", "ignore"); - } - - if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital)) - { - xServiceInstall.SetAttributeValue("Vital", "yes"); - } - - if (!row.IsColumnNull(7)) - { - var dependencies = NullSplitter.Split(row.FieldAsString(7)); - - foreach (var dependency in dependencies) - { - if (0 < dependency.Length) - { - var xServiceDependency = new XElement(Names.ServiceDependencyElement); - - if (dependency.StartsWith("+", StringComparison.Ordinal)) - { - xServiceDependency.SetAttributeValue("Group", "yes"); - xServiceDependency.SetAttributeValue("Id", dependency.Substring(1)); - } - else - { - xServiceDependency.SetAttributeValue("Id", dependency); - } - - xServiceInstall.Add(xServiceDependency); - } - } - } - - this.AddChildToParent("Component", xServiceInstall, row, 11); - this.IndexElement(row, xServiceInstall); - } - } - - /// - /// Decompile the SFPCatalog table. - /// - /// The table to decompile. - private void DecompileSFPCatalogTable(Table table) - { - foreach (var row in table.Rows) - { - var xSfpCatalog = new XElement(Names.SFPCatalogElement, - new XAttribute("Name", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.IndexElement(row, xSfpCatalog); - } - - // nest the SFPCatalog elements - foreach (var row in table.Rows) - { - var xSfpCatalog = this.GetIndexedElement(row); - - if (!row.IsColumnNull(2)) - { - if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2))) - { - xParentSFPCatalog.Add(xSfpCatalog); - } - else - { - xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2)); - - this.RootElement.Add(xSfpCatalog); - } - } - else - { - this.RootElement.Add(xSfpCatalog); - } - } - } - - /// - /// Decompile the Shortcut table. - /// - /// The table to decompile. - private void DecompileShortcutTable(Table table) - { - foreach (var row in table.Rows) - { - var xShortcut = new XElement(Names.ShortcutElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Directory", row.FieldAsString(1)), - XAttributeIfNotNull("Arguments", row, 5), - XAttributeIfNotNull("Description", row, 6), - XAttributeIfNotNull("Hotkey", row, 7), - XAttributeIfNotNull("Icon", row, 8), - XAttributeIfNotNull("IconIndex", row, 9), - XAttributeIfNotNull("WorkingDirectory", row, 11)); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - if (null != names[0] && null != names[1]) - { - xShortcut.SetAttributeValue("ShortName", names[0]); - xShortcut.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xShortcut.SetAttributeValue("Name", names[0]); - } - - if (!row.IsColumnNull(10)) - { - switch (row.FieldAsInteger(10)) - { - case 1: - xShortcut.SetAttributeValue("Show", "normal"); - break; - case 3: - xShortcut.SetAttributeValue("Show", "maximized"); - break; - case 7: - xShortcut.SetAttributeValue("Show", "minimized"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); - break; - } - } - - // Only try to read the MSI 4.0-specific columns if they actually exist - if (15 < row.Fields.Length) - { - if (!row.IsColumnNull(12)) - { - xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12)); - } - - if (null != row[13]) - { - xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13)); - } - - if (null != row[14]) - { - xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14)); - } - - if (null != row[15]) - { - xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15)); - } - } - - this.AddChildToParent("Component", xShortcut, row, 3); - this.IndexElement(row, xShortcut); - } - } - - /// - /// Decompile the Signature table. - /// - /// The table to decompile. - private void DecompileSignatureTable(Table table) - { - foreach (var row in table.Rows) - { - var fileSearch = new XElement(Names.FileSearchElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("MinVersion", row, 2), - XAttributeIfNotNull("MaxVersion", row, 3), - XAttributeIfNotNull("MinSize", row, 4), - XAttributeIfNotNull("MaxSize", row, 5), - XAttributeIfNotNull("Languages", row, 8)); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - if (null != names[0]) - { - // it is permissable to just have a long name - if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1]) - { - fileSearch.SetAttributeValue("Name", names[0]); - } - else - { - fileSearch.SetAttributeValue("ShortName", names[0]); - } - } - - if (null != names[1]) - { - fileSearch.SetAttributeValue("Name", names[1]); - } - - if (!row.IsColumnNull(6)) - { - fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6))); - } - - if (!row.IsColumnNull(7)) - { - fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7))); - } - - this.IndexElement(row, fileSearch); - } - } - - /// - /// Decompile the TargetFiles_OptionalData table. - /// - /// The table to decompile. - private void DecompileTargetFiles_OptionalDataTable(Table table) - { - foreach (var row in table.Rows) - { - if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile)) - { - xPatchTargetFile = new XElement(Names.TargetFileElement, - new XAttribute("Id", row.FieldAsString(1))); - - if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0))) - { - xTargetImage.Add(xPatchTargetFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); - } - - this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile); - } - - AddSymbolPaths(row, 2, xPatchTargetFile); - - if (!row.IsColumnNull(3) && !row.IsColumnNull(4)) - { - var ignoreOffsets = row.FieldAsString(3).Split(','); - var ignoreLengths = row.FieldAsString(4).Split(','); - - if (ignoreOffsets.Length == ignoreLengths.Length) - { - for (var i = 0; i < ignoreOffsets.Length; i++) - { - var xIgnoreRange = new XElement(Names.IgnoreRangeElement); - - if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); - } - - if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); - } - - xPatchTargetFile.Add(xIgnoreRange); - } - } - else - { - // TODO: warn - } - } - else if (!row.IsColumnNull(3) || !row.IsColumnNull(4)) - { - // TODO: warn about mismatch between columns - } - - // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - } - } - - /// - /// Decompile the TargetImages table. - /// - /// The table to decompile. - private void DecompileTargetImagesTable(Table table) - { - foreach (var row in table.Rows) - { - var xTargetImage = new XElement(Names.TargetImageElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1)), - new XAttribute("Order", row.FieldAsInteger(4)), - XAttributeIfNotNull("Validation", row, 5)); - - AddSymbolPaths(row, 2, xTargetImage); - - if (0 != row.FieldAsInteger(6)) - { - xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes"); - } - - this.AddChildToParent("UpgradedImages", xTargetImage, row, 3); - this.IndexElement(row, xTargetImage); - } - } - - /// - /// Decompile the TextStyle table. - /// - /// The table to decompile. - private void DecompileTextStyleTable(Table table) - { - foreach (var row in table.Rows) - { - var xTextStyle = new XElement(Names.TextStyleElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("FaceName", row.FieldAsString(1)), - new XAttribute("Size", row.FieldAsString(2))); - - if (!row.IsColumnNull(3)) - { - var color = row.FieldAsInteger(3); - - xTextStyle.SetAttributeValue("Red", color & 0xFF); - xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8); - xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16); - } - - if (!row.IsColumnNull(4)) - { - var styleBits = row.FieldAsInteger(4); - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold)) - { - xTextStyle.SetAttributeValue("Bold", "yes"); - } - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic)) - { - xTextStyle.SetAttributeValue("Italic", "yes"); - } - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline)) - { - xTextStyle.SetAttributeValue("Underline", "yes"); - } - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike)) - { - xTextStyle.SetAttributeValue("Strike", "yes"); - } - } - - this.UIElement.Add(xTextStyle); - } - } - - /// - /// Decompile the TypeLib table. - /// - /// The table to decompile. - private void DecompileTypeLibTable(Table table) - { - foreach (var row in table.Rows) - { - var id = row.FieldAsString(0); - var xTypeLib = new XElement(Names.TypeLibElement, - new XAttribute("Advertise", "yes"), - new XAttribute("Id", id), - new XAttribute("Language", row.FieldAsInteger(1)), - XAttributeIfNotNull("Description", row, 4), - XAttributeIfNotNull("HelpDirectory", row, 5)); - - if (!row.IsColumnNull(3)) - { - var version = row.FieldAsInteger(3); - - if (65536 == version) - { - this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id)); - } - - xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8); - xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF); - } - - if (!row.IsColumnNull(7)) - { - xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7)); - } - - // nested under the appropriate File element in FinalizeFileTable - this.IndexElement(row, xTypeLib); - } - } - - /// - /// Decompile the Upgrade table. - /// - /// The table to decompile. - private void DecompileUpgradeTable(Table table) - { - var xUpgrades = new Dictionary(); - - foreach (UpgradeRow upgradeRow in table.Rows) - { - if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) - { - continue; // MajorUpgrade rows processed in FinalizeUpgradeTable - } - - if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade)) - { - xUpgrade = new XElement(Names.UpgradeElement, - new XAttribute("Id", upgradeRow.UpgradeCode)); - - this.RootElement.Add(xUpgrade); - xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade); - } - - var xUpgradeVersion = new XElement(Names.UpgradeVersionElement, - new XAttribute("Id", upgradeRow.UpgradeCode), - new XAttribute("Property", upgradeRow.ActionProperty)); - - if (null != upgradeRow.VersionMin) - { - xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin); - } - - if (null != upgradeRow.VersionMax) - { - xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax); - } - - if (null != upgradeRow.Language) - { - xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) - { - xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect)) - { - xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) - { - xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive)) - { - xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) - { - xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive)) - { - xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes"); - } - - if (null != upgradeRow.Remove) - { - xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove); - } - - xUpgrade.Add(xUpgradeVersion); - } - } - - /// - /// Decompile the UpgradedFiles_OptionalData table. - /// - /// The table to decompile. - private void DecompileUpgradedFiles_OptionalDataTable(Table table) - { - foreach (var row in table.Rows) - { - var xUpgradeFile = new XElement(Names.UpgradeFileElement, - new XAttribute("File", row.FieldAsString(1)), - new XAttribute("Ignore", "no")); - - AddSymbolPaths(row, 2, xUpgradeFile); - - if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3)) - { - xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes"); - } - - if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4)) - { - xUpgradeFile.SetAttributeValue("WholeFile", "yes"); - } - - this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); - } - } - - /// - /// Decompile the UpgradedFilesToIgnore table. - /// - /// The table to decompile. - private void DecompileUpgradedFilesToIgnoreTable(Table table) - { - foreach (var row in table.Rows) - { - if ("*" != row.FieldAsString(0)) - { - var xUpgradeFile = new XElement(Names.UpgradeFileElement, - new XAttribute("File", row.FieldAsString(1)), - new XAttribute("Ignore", "yes")); - - this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); - } - else - { - this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); - } - } - } - - /// - /// Decompile the UpgradedImages table. - /// - /// The table to decompile. - private void DecompileUpgradedImagesTable(Table table) - { - foreach (var row in table.Rows) - { - var xUpgradeImage = new XElement(Names.UpgradeImageElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1)), - XAttributeIfNotNull("SourcePatch", row, 2)); - - AddSymbolPaths(row, 3, xUpgradeImage); - - this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4); - this.IndexElement(row, xUpgradeImage); - } - } - - private static void AddSymbolPaths(Row row, int column, XElement xParent) - { - if (!row.IsColumnNull(column)) - { - var symbolPaths = row.FieldAsString(column).Split(';'); - - foreach (var symbolPath in symbolPaths) - { - var xSymbolPath = new XElement(Names.SymbolPathElement, - new XAttribute("Path", symbolPath)); - - xParent.Add(xSymbolPath); - } - } - } - - /// - /// Decompile the UIText table. - /// - /// The table to decompile. - private void DecompileUITextTable(Table table) - { - foreach (var row in table.Rows) - { - var xUiText = new XElement(Names.UITextElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Value", row.FieldAsString(1))); - - this.UIElement.Add(xUiText); - } - } - - /// - /// Decompile the Verb table. - /// - /// The table to decompile. - private void DecompileVerbTable(Table table) - { - foreach (var row in table.Rows) - { - var verb = new XElement(Names.VerbElement, - new XAttribute("Id", row.FieldAsString(1)), - XAttributeIfNotNull("Sequence", row, 2), - XAttributeIfNotNull("Command", row, 3), - XAttributeIfNotNull("Argument", row, 4)); - - this.IndexElement(row, verb); - } - } - - /// - /// Gets the RegistryRootType from an integer representation of the root. - /// - /// The source line information for the root. - /// The name of the table containing the field. - /// The field containing the root value. - /// The strongly-typed representation of the root. - /// true if the value could be converted; false otherwise. - private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType) - { - switch (Convert.ToInt32(field.Data)) - { - case (-1): - registryRootType = "HKMU"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - registryRootType = "HKCR"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - registryRootType = "HKCU"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - registryRootType = "HKLM"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - registryRootType = "HKU"; - return true; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); - registryRootType = null; // assign anything to satisfy the out parameter - return false; - } - } - - /// - /// Set the primary feature for a component. - /// - /// The row which specifies a primary feature. - /// The index of the column contaning the feature identifier. - /// The index of the column containing the component identifier. - private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) - { - // only products contain primary features - if (OutputType.Product == this.OutputType) - { - var featureField = row.Fields[featureColumnIndex]; - var componentField = row.Fields[componentColumnIndex]; - - if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data))) - { - xComponentRef.SetAttributeValue("Primary", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); - } - } - } - - /// - /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. - /// - /// The collection of all tables. - private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables) - { - var sequenceRemoveExistingProducts = 0; - var sequenceInstallValidate = 0; - var sequenceInstallInitialize = 0; - var sequenceInstallFinalize = 0; - var sequenceInstallExecute = 0; - var sequenceInstallExecuteAgain = 0; - - var installExecuteSequenceTable = tables["InstallExecuteSequence"]; - if (null != installExecuteSequenceTable) - { - var removeExistingProductsRow = -1; - for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) - { - var row = installExecuteSequenceTable.Rows[i]; - var action = row.FieldAsString(0); - var sequence = row.FieldAsInteger(2); - - switch (action) - { - case "RemoveExistingProducts": - sequenceRemoveExistingProducts = sequence; - removeExistingProductsRow = i; - break; - case "InstallValidate": - sequenceInstallValidate = sequence; - break; - case "InstallInitialize": - sequenceInstallInitialize = sequence; - break; - case "InstallExecute": - sequenceInstallExecute = sequence; - break; - case "InstallExecuteAgain": - sequenceInstallExecuteAgain = sequence; - break; - case "InstallFinalize": - sequenceInstallFinalize = sequence; - break; - } - } - - installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); - } - - if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) - { - return "afterInstallValidate"; - } - else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) - { - return "afterInstallInitialize"; - } - else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) - { - return "afterInstallExecute"; - } - else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) - { - return "afterInstallExecuteAgain"; - } - else - { - return "afterInstallFinalize"; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs deleted file mode 100644 index db65bbf7..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs +++ /dev/null @@ -1,160 +0,0 @@ -namespace WixToolset.Core.WindowsInstaller.Decompile -{ - using System.Xml.Linq; - - internal static class Names - { - public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs"; - - public static readonly XName WixElement = WxsNamespace + "Wix"; - - public static readonly XName PackageElement = WxsNamespace + "Package"; - public static readonly XName ModuleElement = WxsNamespace + "Module"; - public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; - - public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation"; - - public static readonly XName CustomElement = WxsNamespace + "Custom"; - - public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; - public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence"; - public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence"; - public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence"; - public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence"; - - public static readonly XName AppSearchElement = WxsNamespace + "AppSearch"; - - public static readonly XName PropertyElement = WxsNamespace + "Property"; - - public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange"; - public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile"; - - public static readonly XName FileElement = WxsNamespace + "File"; - - public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; - public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; - - public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; - public static readonly XName UIElement = WxsNamespace + "UI"; - - public static readonly XName AppIdElement = WxsNamespace + "AppId"; - - public static readonly XName ControlElement = WxsNamespace + "Control"; - - public static readonly XName BillboardElement = WxsNamespace + "Billboard"; - public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction"; - - public static readonly XName BinaryElement = WxsNamespace + "Binary"; - - public static readonly XName ClassElement = WxsNamespace + "Class"; - - public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask"; - - public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox"; - - public static readonly XName ListItemElement = WxsNamespace + "ListItem"; - - public static readonly XName ConditionElement = WxsNamespace + "Condition"; - public static readonly XName PublishElement = WxsNamespace + "Publish"; - public static readonly XName CustomTableElement = WxsNamespace + "CustomTable"; - public static readonly XName ColumnElement = WxsNamespace + "Column"; - public static readonly XName RowElement = WxsNamespace + "Row"; - public static readonly XName DataElement = WxsNamespace + "Data"; - public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder"; - - public static readonly XName CustomActionElement = WxsNamespace + "CustomAction"; - - public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch"; - public static readonly XName ComponentElement = WxsNamespace + "Component"; - - public static readonly XName LevelElement = WxsNamespace + "Level"; - public static readonly XName DialogElement = WxsNamespace + "Dialog"; - public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory"; - public static readonly XName DirectoryElement = WxsNamespace + "Directory"; - public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; - public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; - public static readonly XName EnvironmentElement = WxsNamespace + "Environment"; - public static readonly XName ErrorElement = WxsNamespace + "Error"; - public static readonly XName SubscribeElement = WxsNamespace + "Subscribe"; - public static readonly XName ExtensionElement = WxsNamespace + "Extension"; - public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile"; - public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath"; - public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange"; - - public static readonly XName FeatureElement = WxsNamespace + "Feature"; - public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef"; - public static readonly XName SFPFileElement = WxsNamespace + "SFPFile"; - public static readonly XName IconElement = WxsNamespace + "Icon"; - public static readonly XName FamilyElement = WxsNamespace + "Family"; - public static readonly XName IniFileElement = WxsNamespace + "IniFile"; - public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch"; - public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent"; - public static readonly XName LaunchElement = WxsNamespace + "Launch"; - public static readonly XName ListBoxElement = WxsNamespace + "ListBox"; - public static readonly XName ListViewElement = WxsNamespace + "ListView"; - public static readonly XName PermissionElement = WxsNamespace + "Permission"; - public static readonly XName MediaElement = WxsNamespace + "Media"; - public static readonly XName MIMEElement = WxsNamespace + "MIME"; - public static readonly XName ConfigurationElement = WxsNamespace + "Configuration"; - public static readonly XName DependencyElement = WxsNamespace + "Dependency"; - public static readonly XName ExclusionElement = WxsNamespace + "Exclusion"; - public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable"; - public static readonly XName SubstitutionElement = WxsNamespace + "Substitution"; - public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate"; - public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature"; - public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer"; - public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI"; - public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource"; - public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx"; - public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates"; - public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates"; - public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty"; - public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource"; - public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver"; - public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator"; - public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata"; - public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions"; - public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty"; - public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence"; - public static readonly XName ProgIdElement = WxsNamespace + "ProgId"; - public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch"; - public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode"; - public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty"; - public static readonly XName CategoryElement = WxsNamespace + "Category"; - public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton"; - public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup"; - public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey"; - public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue"; - public static readonly XName MultiStringElement = WxsNamespace + "MultiString"; - public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch"; - public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder"; - public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile"; - public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey"; - public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue"; - public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost"; - public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl"; - public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument"; - public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall"; - public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency"; - public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog"; - public static readonly XName ShortcutElement = WxsNamespace + "Shortcut"; - public static readonly XName FileSearchElement = WxsNamespace + "FileSearch"; - public static readonly XName TargetFileElement = WxsNamespace + "TargetFile"; - public static readonly XName TargetImageElement = WxsNamespace + "TargetImage"; - public static readonly XName TextStyleElement = WxsNamespace + "TextStyle"; - public static readonly XName TypeLibElement = WxsNamespace + "TypeLib"; - public static readonly XName UpgradeElement = WxsNamespace + "Upgrade"; - public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion"; - public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile"; - public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage"; - public static readonly XName UITextElement = WxsNamespace + "UIText"; - public static readonly XName VerbElement = WxsNamespace + "Verb"; - public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck"; - public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef"; - public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive"; - public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef"; - public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef"; - public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade"; - //public static readonly XName Element = WxsNamespace + ""; - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Differ.cs b/src/WixToolset.Core.WindowsInstaller/Differ.cs deleted file mode 100644 index 304d0152..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Differ.cs +++ /dev/null @@ -1,610 +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 DELETE - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using WixToolset.Core.WindowsInstaller.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Creates a transform by diffing two outputs. - /// - public sealed class Differ - { - private readonly List inspectorExtensions; - private bool showPedanticMessages; - private bool suppressKeepingSpecialRows; - private bool preserveUnchangedRows; - private const char sectionDelimiter = '/'; - private readonly IMessaging messaging; - private SummaryInformationStreams transformSummaryInfo; - - /// - /// Instantiates a new Differ class. - /// - public Differ(IMessaging messaging) - { - this.inspectorExtensions = new List(); - this.messaging = messaging; - } - - /// - /// 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 WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput) - { - return this.Diff(targetOutput, updatedOutput, 0); - } - - /// - /// Creates a transform by diffing two outputs. - /// - /// The target output. - /// The updated output. - /// - /// The transform. - public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) - { - WindowsInstallerData transform = new WindowsInstallerData(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.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); - if (null != updatedOutput.SourceLineNumbers) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); - } - } - - // compare the output types - if (targetOutput.Type != updatedOutput.Type) - { - throw new WixException(ErrorMessages.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; - } - - /// - /// 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.messaging.Write(ErrorMessages.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(WindowsInstallerData 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.messaging.Write(ErrorMessages.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.messaging.Write(ErrorMessages.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.messaging.Write(ErrorMessages.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; } - } - } -} - -#endif diff --git a/src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs deleted file mode 100644 index 8305b5e6..00000000 --- a/src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper - { - private readonly IBackendHelper backendHelper; - - public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) - { - this.backendHelper = serviceProvider.GetService(); - } - - #region IBackendHelper interfaces - - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); - - public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); - - public string CreateGuid() => this.backendHelper.CreateGuid(); - - public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); - - public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); - - public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); - - public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); - - public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); - - public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); - - public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); - - public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); - - public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); - - public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); - - public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); - - public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); - - public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); - - #endregion - - #region IWindowsInstallerBackendHelper interfaces - - public Row CreateRow(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinition tableDefinition) - { - var table = data.EnsureTable(tableDefinition); - - var row = table.CreateRow(symbol.SourceLineNumbers); - row.SectionId = section.Id; - - return row; - } - - public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions) - { - var tableDefinition = tableDefinitions.FirstOrDefault(t => t.SymbolDefinition?.Name == symbol.Definition.Name); - if (tableDefinition == null) - { - return false; - } - - var row = this.CreateRow(section, symbol, data, tableDefinition); - var rowOffset = 0; - - if (tableDefinition.SymbolIdIsPrimaryKey) - { - row[0] = symbol.Id.Id; - rowOffset = 1; - } - - for (var i = 0; i < symbol.Fields.Length; ++i) - { - if (i < tableDefinition.Columns.Length) - { - var column = tableDefinition.Columns[i + rowOffset]; - - switch (column.Type) - { - case ColumnType.Number: - row[i + rowOffset] = column.Nullable ? symbol.AsNullableNumber(i) : symbol.AsNumber(i); - break; - - default: - row[i + rowOffset] = symbol.AsString(i); - break; - } - } - } - - return true; - } - - #endregion - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs deleted file mode 100644 index 57f2f753..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.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.Msi; - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class InscribeMsiPackageCommand - { - public InscribeMsiPackageCommand(IInscribeContext context) - { - this.Context = context; - this.Messaging = context.ServiceProvider.GetService(); - this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); - this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - } - - private IInscribeContext Context { get; } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { 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 - var foundUnsignedExternals = false; - var shouldCommit = false; - - var attributes = File.GetAttributes(this.Context.InputFilePath); - if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) - { - this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath)); - return shouldCommit; - } - - using (var 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 - var codepage = 1252; - - // list of certificates for this database (hash/identifier) - var certificates = new Dictionary(); - - // Reset the in-memory tables for this new database - var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); - var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); - - // If any digital signature records exist that are not of the media type, preserve them - if (database.TableExists("MsiDigitalSignature")) - { - using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) - { - foreach (var digitalSignatureRecord in digitalSignatureView.Records) - { - Row digitalSignatureRow = null; - digitalSignatureRow = digitalSignatureTable.CreateRow(null); - - var table = digitalSignatureRecord.GetString(0); - var 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 - var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); - var hashFileName = String.Concat(table, ".", signObject, ".bin"); - - Directory.CreateDirectory(hashPath); - hashPath = Path.Combine(hashPath, hashFileName); - - using (var fs = File.Create(hashPath)) - { - int bytesRead; - var 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 (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) - { - foreach (var digitalCertificateRecord in digitalCertificateView.Records) - { - var 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 - var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); - Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer")); - - using (var fs = File.Create(certPath)) - { - int bytesRead; - var 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 - var 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 - var cert = X509Certificate.CreateFromCertFile(certPath); - var cert2 = new X509Certificate2(cert); - - certificates.Add(cert2.Thumbprint, certificateId); - } - } - } - - using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) - { - foreach (var mediaRecord in mediaView.Records) - { - X509Certificate2 cert2 = null; - Row digitalSignatureRow = null; - - var 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; - } - - var cabId = mediaRecord.GetString(1); // get the ID of the cab - var 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.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath)); - continue; - } - - try - { - // Get the certificate from the cab - var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); - cert2 = new X509Certificate2(signedFileCert); - } - catch (System.Security.Cryptography.CryptographicException e) - { - var 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.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); - } - else // otherwise, generic error - { - this.Messaging.Write(ErrorMessages.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 - var certificateGeneratedId = this.WindowsInstallerBackendHelper.GenerateIdentifier("cer", cert2.Thumbprint); - - // Add it to our "add to MsiDigitalCertificate" table dictionary - var 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 - var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); - Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer")); - File.Delete(certPath); - - using (var 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) - { - var command = new CreateIdtFileCommand(this.Messaging, digitalCertificateTable, codepage, this.Context.IntermediateFolder, true); - command.Execute(); - - database.Import(command.IdtPath); - shouldCommit = true; - } - - if (digitalSignatureTable.Rows.Count > 0) - { - var command = new CreateIdtFileCommand(this.Messaging, digitalSignatureTable, codepage, this.Context.IntermediateFolder, true); - command.Execute(); - - database.Import(command.IdtPath); - 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 not all of them were signed, give a warning - if (foundUnsignedExternals) - { - this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath)); - } - - if (shouldCommit) - { - database.Commit(); - } - } - - return shouldCommit; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Melter.cs b/src/WixToolset.Core.WindowsInstaller/Melter.cs deleted file mode 100644 index 29e19e49..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Melter.cs +++ /dev/null @@ -1,399 +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.CodeDom.Compiler; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Globalization; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Data; - - /// - /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. - /// - public sealed class Melter - { -#if TODO_MELT - private MelterCore core; - private Decompiler decompiler; - - private Wix.ComponentGroup componentGroup; - private Wix.DirectoryRef primaryDirectoryRef; - private Wix.Fragment fragment; - - private string id; - private string moduleId; - private const string nullGuid = "{00000000-0000-0000-0000-000000000000}"; - - public string Id - { - get { return this.id; } - set { this.id = value; } - } - - public Decompiler Decompiler - { - get { return this.decompiler; } - set { this.decompiler = value; } - } - - /// - /// Creates a new melter object. - /// - /// The decompiler to use during the melting process. - /// The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id - public Melter(Decompiler decompiler, string id) - { - this.core = new MelterCore(); - - this.componentGroup = new Wix.ComponentGroup(); - this.fragment = new Wix.Fragment(); - this.primaryDirectoryRef = new Wix.DirectoryRef(); - - this.decompiler = decompiler; - this.id = id; - - if (null == this.decompiler) - { - this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process")); - } - } - - /// - /// Converts a Module wixout into a ComponentGroup. - /// - /// The output object representing the unbound merge module to melt. - /// The converted Module as a ComponentGroup. - public Wix.Wix Melt(Output wixout) - { - this.moduleId = GetModuleId(wixout); - - // Assign the default componentGroupId if none was specified - if (null == this.id) - { - this.id = this.moduleId; - } - - this.componentGroup.Id = this.id; - this.primaryDirectoryRef.Id = this.id; - - PreDecompile(wixout); - - wixout.Type = OutputType.Product; - this.decompiler.TreatProductAsModule = true; - Wix.Wix wix = this.decompiler.Decompile(wixout); - - if (null == wix) - { - return wix; - } - - ConvertModule(wix); - - return wix; - } - - /// - /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment. - /// - /// The output object representing an unbound merge module. - private void ConvertModule(Wix.Wix wix) - { - Wix.Product product = Melter.GetProduct(wix); - - List customActionsRemoved = new List(); - Dictionary customsToRemove = new Dictionary(); - - foreach (Wix.ISchemaElement child in product.Children) - { - Wix.Directory childDir = child as Wix.Directory; - if (null != childDir) - { - bool isTargetDir = this.WalkDirectory(childDir); - if (isTargetDir) - { - continue; - } - } - else - { - Wix.Dependency childDep = child as Wix.Dependency; - if (null != childDep) - { - this.AddPropertyRef(childDep.RequiredId); - continue; - } - else if (child is Wix.Package) - { - continue; - } - else if (child is Wix.CustomAction) - { - Wix.CustomAction customAction = child as Wix.CustomAction; - string directoryId; - if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id) - { - customActionsRemoved.Add(customAction.Id); - continue; - } - } - else if (child is Wix.InstallExecuteSequence) - { - Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence; - - foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children) - { - Wix.Custom custom = sequenceChild as Wix.Custom; - string directoryId; - if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId)) - { - customsToRemove.Add(custom, installExecuteSequence); - } - } - } - } - - this.fragment.AddChild(child); - } - - // For any customaction that we removed, also remove the scheduling of that action. - foreach (Wix.Custom custom in customsToRemove.Keys) - { - if (customActionsRemoved.Contains(custom.Action)) - { - ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom); - } - } - - AddProperty(this.moduleId, this.id); - - wix.RemoveChild(product); - wix.AddChild(this.fragment); - - this.fragment.AddChild(this.componentGroup); - this.fragment.AddChild(this.primaryDirectoryRef); - } - - /// - /// Gets the module from the Wix object. - /// - /// The Wix object. - /// The Module in the Wix object, null if no Module was found - private static Wix.Product GetProduct(Wix.Wix wix) - { - foreach (Wix.ISchemaElement element in wix.Children) - { - Wix.Product productElement = element as Wix.Product; - if (null != productElement) - { - return productElement; - } - } - return null; - } - - /// - /// Adds a PropertyRef to the main Fragment. - /// - /// Id of the PropertyRef. - private void AddPropertyRef(string propertyRefId) - { - Wix.PropertyRef propertyRef = new Wix.PropertyRef(); - propertyRef.Id = propertyRefId; - this.fragment.AddChild(propertyRef); - } - - /// - /// Adds a Property to the main Fragment. - /// - /// Id of the Property. - /// Value of the Property. - private void AddProperty(string propertyId, string value) - { - Wix.Property property = new Wix.Property(); - property.Id = propertyId; - property.Value = value; - this.fragment.AddChild(property); - } - - /// - /// Walks a directory structure obtaining Component Id's and Standard Directory Id's. - /// - /// The Directory to walk. - /// true if the directory is TARGETDIR. - private bool WalkDirectory(Wix.Directory directory) - { - bool isTargetDir = false; - if ("TARGETDIR" == directory.Id) - { - isTargetDir = true; - } - - string standardDirectoryId = null; - if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir) - { - this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId)); - } - - foreach (Wix.ISchemaElement child in directory.Children) - { - Wix.Directory childDir = child as Wix.Directory; - if (null != childDir) - { - if (isTargetDir) - { - this.primaryDirectoryRef.AddChild(child); - } - this.WalkDirectory(childDir); - } - else - { - Wix.Component childComponent = child as Wix.Component; - if (null != childComponent) - { - if (isTargetDir) - { - this.primaryDirectoryRef.AddChild(child); - } - this.AddComponentRef(childComponent); - } - } - } - - return isTargetDir; - } - - /// - /// Gets the module Id out of the Output object. - /// - /// The output object. - /// The module Id from the Output object. - private string GetModuleId(Output wixout) - { - // get the moduleId from the wixout - Table moduleSignatureTable = wixout.Tables["ModuleSignature"]; - if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count) - { - this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature")); - } - return moduleSignatureTable.Rows[0].Fields[0].Data.ToString(); - } - - /// - /// Determines if the directory Id starts with a standard directory id. - /// - /// The directory id. - /// The standard directory id. - /// true if the directory starts with a standard directory id. - private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId) - { - standardDirectoryId = null; - foreach (string id in WindowsInstallerStandard.GetStandardDirectories()) - { - if (directoryId.StartsWith(id, StringComparison.Ordinal)) - { - standardDirectoryId = id; - return true; - } - } - return false; - } - - /// - /// Adds a ComponentRef to the main ComponentGroup. - /// - /// The component to add. - private void AddComponentRef(Wix.Component component) - { - Wix.ComponentRef componentRef = new Wix.ComponentRef(); - componentRef.Id = component.Id; - this.componentGroup.AddChild(componentRef); - } - - /// - /// Adds a SetProperty CA for a Directory. - /// - /// The Id of the Property to set. - /// The value to set the Property to. - private void AddSetPropertyCustomAction(string propertyId, string value) - { - // Add the action - Wix.CustomAction customAction = new Wix.CustomAction(); - customAction.Id = propertyId; - customAction.Property = propertyId; - customAction.Value = value; - this.fragment.AddChild(customAction); - - // Schedule the action - Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence(); - Wix.Custom custom = new Wix.Custom(); - custom.Action = customAction.Id; - custom.Before = "CostInitialize"; - installExecuteSequence.AddChild(custom); - this.fragment.AddChild(installExecuteSequence); - } - - /// - /// Does any operations to the wixout that would need to be done before decompiling. - /// - /// The output object representing the unbound merge module. - private void PreDecompile(Output wixout) - { - string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id); - - foreach (Table table in wixout.Tables) - { - // Determine if the table has a feature foreign key - bool hasFeatureForeignKey = false; - foreach (ColumnDefinition columnDef in table.Definition.Columns) - { - if (null != columnDef.KeyTable) - { - string[] keyTables = columnDef.KeyTable.Split(';'); - foreach (string keyTable in keyTables) - { - if ("Feature" == keyTable) - { - hasFeatureForeignKey = true; - break; - } - } - } - } - - // If this table has no foreign keys to the feature table, skip it. - if (!hasFeatureForeignKey) - { - continue; - } - - // Go through all the rows and replace the null guid with the wix variable - // for columns that are foreign keys into the feature table. - foreach (Row row in table.Rows) - { - foreach (Field field in row.Fields) - { - if (null != field.Column.KeyTable) - { - string[] keyTables = field.Column.KeyTable.Split(';'); - foreach (string keyTable in keyTables) - { - if ("Feature" == keyTable) - { - field.Data = field.Data.ToString().Replace(nullGuid, wixVariable); - break; - } - } - } - } - } - } - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MelterCore.cs b/src/WixToolset.Core.WindowsInstaller/MelterCore.cs deleted file mode 100644 index 034c9465..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MelterCore.cs +++ /dev/null @@ -1,32 +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 WixToolset.Data; - - /// - /// Melts a Module Wix document into a ComponentGroup representation. - /// - public sealed class MelterCore - { -#if TODO_MELT - /// - /// Gets whether the melter core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError - { - get { return Messaging.Instance.EncounteredError; } - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs deleted file mode 100644 index 3bd58c25..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.WindowsInstaller.Decompile; - using WixToolset.Core.WindowsInstaller.Inscribe; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class MsiBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - IBindResult result = null; - var dispose = true; - try - { - var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); - result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - dispose = false; - return result; - } - finally - { - if (dispose) - { - result?.Dispose(); - } - } - } - - public IDecompileResult Decompile(IDecompileContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendDecompile(context); - } - - var command = new DecompileMsiOrMsmCommand(context, backendExtensions); - var result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendDecompile(result); - } - - return result; - } - - public bool Inscribe(IInscribeContext context) - { - var command = new InscribeMsiPackageCommand(context); - return command.Execute(); - } - - public Intermediate 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 deleted file mode 100644 index 4927ee8c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.WindowsInstaller.Decompile; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class MsmBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - IBindResult result = null; - try - { - var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); - result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - return result; - } - catch - { - result?.Dispose(); - throw; - } - } - - public IDecompileResult Decompile(IDecompileContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendDecompile(context); - } - - var command = new DecompileMsiOrMsmCommand(context, backendExtensions); - var result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendDecompile(result); - } - - return result; - } - - public bool Inscribe(IInscribeContext context) => false; - - public Intermediate 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 deleted file mode 100644 index c46b6027..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class MspBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var messaging = context.ServiceProvider.GetService(); - - var backendHelper = context.ServiceProvider.GetService(); - - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - // Create transforms named in patch transforms. - IEnumerable patchTransforms; - { - var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); - patchTransforms = command.Execute(); - } - - // Enhance the intermediate by attaching the created patch transforms. - IEnumerable subStorages; - { - var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms); - subStorages = command.Execute(); - } - - // Create WindowsInstallerData with patch metdata and transforms as sub-storages - // Create MSP from WindowsInstallerData - IBindResult result = null; - try - { - var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); - result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - return result; - } - catch - { - result?.Dispose(); - throw; - } - } - - public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); - - public bool Inscribe(IInscribeContext context) => throw new NotImplementedException(); - - public Intermediate Unbind(IUnbindContext context) - { -#if TODO_PATCHING - Output patch; - - // patch files are essentially database files (use a special flag to let the API know its a patch file) - try - { - using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false); - patch = unbindCommand.Execute(); - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath)); - } - - throw; - } - - // retrieve the transforms (they are in substorages) - using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite)) - { - Table summaryInformationTable = patch.Tables["_SummaryInformation"]; - foreach (Row row in summaryInformationTable.Rows) - { - if (8 == (int)row[0]) // PID_LASTAUTHOR - { - string value = (string)row[1]; - - foreach (string decoratedSubStorageName in value.Split(';')) - { - string subStorageName = decoratedSubStorageName.Substring(1); - string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); - - // ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); - - // copy the substorage to a new storage for the transform file - using (Storage subStorage = storage.OpenStorage(subStorageName)) - { - using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) - { - subStorage.CopyTo(transformStorage); - } - } - - // unbind the transform - var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder); - var transform = unbindCommand.Execute(); - - patch.SubStorages.Add(new SubStorage(subStorageName, transform)); - } - - break; - } - } - } - - // extract the files from the cabinets - // TODO: use per-transform export paths for support of multi-product patches - if (null != context.ExportBasePath && !context.SuppressExtractCabinets) - { - using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - foreach (SubStorage subStorage in patch.SubStorages) - { - // only patch transforms should carry files - if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) - { - var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder); - extractCommand.Execute(); - } - } - } - } - - return patch; -#endif - throw new NotImplementedException(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs deleted file mode 100644 index a6d86c10..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MstBackend.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.Core.WindowsInstaller -{ - using System; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class MstBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { -#if TODO_PATCHING - 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()); -#endif - throw new NotImplementedException(); - } - - public IDecompileResult Decompile(IDecompileContext context) - { - throw new NotImplementedException(); - } - - public bool Inscribe(IInscribeContext context) - { - throw new NotImplementedException(); - } - - public Intermediate Unbind(IUnbindContext context) - { - var command = new UnbindMsiOrMsmCommand(context); - return command.Execute(); - } - } -} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs b/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs deleted file mode 100644 index ad7764bc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.WindowsInstaller; - - /// - /// A dictionary of rows. Unlike the RowIndexedList this - /// will throw when multiple rows with the same key are added. - /// - internal sealed class RowDictionary : Dictionary where T : Row - { - /// - /// Creates an empty . - /// - public RowDictionary() - : base(StringComparer.InvariantCulture) - { - } - - /// - /// Creates and populates a with the rows from the given . - /// - /// The table to index. - /// - /// Rows added to the index are not automatically added to the given . - /// - public RowDictionary(Table table) - : this() - { - if (null != table) - { - foreach (T row in table.Rows) - { - this.Add(row); - } - } - } - - /// - /// Adds a row to the dictionary using the row key. - /// - /// Row to add to the dictionary. - public void Add(T row) - { - this.Add(row.GetKey(), row); - } - - /// - /// Gets the row by integer key. - /// - /// Integer key to look up. - /// Row or null if key is not found. - public T Get(int key) - { - return this.Get(key.ToString()); - } - - /// - /// Gets the row by string key. - /// - /// String key to look up. - /// Row or null if key is not found. - public T Get(string key) - { - return this.TryGetValue(key, out var result) ? result : null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs deleted file mode 100644 index 8f52bed9..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.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.Core.WindowsInstaller.Unbind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - - internal class ExtractCabinetsCommand - { - public ExtractCabinetsCommand(WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false) - { - this.Output = output; - this.Database = database; - this.InputFilePath = inputFilePath; - this.ExportBasePath = exportBasePath; - this.IntermediateFolder = intermediateFolder; - this.TreatOutputAsModule = treatOutputAsModule; - } - - public string[] ExtractedFiles { get; private set; } - - private WindowsInstallerData Output { get; } - - private Database Database { get; } - - private string InputFilePath { get; } - - private string ExportBasePath { get; } - - private string IntermediateFolder { get; } - - public bool TreatOutputAsModule { get; } - - public void Execute() - { - var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); - var cabinetFiles = new List(); - var embeddedCabinets = new SortedList(); - - // index all of the cabinet files - if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) - { - 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 (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) - { - foreach (int diskId in embeddedCabinets.Keys) - { - using (var record = new Record(1)) - { - record.SetString(1, (string)embeddedCabinets[diskId]); - streamsView.Execute(record); - } - - using (var record = streamsView.Fetch()) - { - if (null != record) - { - // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, - // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work - var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); - - // ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); - - using (var fs = File.Create(cabinetFile)) - { - int bytesRead; - var 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) - { - // ensure the directory exists or extraction will fail - Directory.CreateDirectory(this.ExportBasePath); - - foreach (var cabinetFile in cabinetFiles) - { - try - { - var cabinet = new Cabinet(cabinetFile); - this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); - } - catch (FileNotFoundException) - { - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); - } - } - } - else - { - this.ExtractedFiles = new string[0]; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs deleted file mode 100644 index b510690e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ /dev/null @@ -1,789 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Unbind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text.RegularExpressions; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class UnbindDatabaseCommand - { - private List exportedFiles; - - public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - 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 = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - } - - public IMessaging Messaging { get; } - - public IBackendHelper BackendHelper { 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; } - - public IEnumerable ExportedFiles => this.exportedFiles; - - private int SectionCount { get; set; } - - public WindowsInstallerData Execute() - { - this.exportedFiles = new List(); - - string modularizationGuid = null; - var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); - View validationView = null; - - // set the output type - output.Type = this.OutputType; - - Directory.CreateDirectory(this.IntermediateFolder); - - // get the codepage - this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); - using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) - { - string line; - - while (null != (line = sr.ReadLine())) - { - var 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 (var summaryInformation = new SummaryInformation(this.Database)) - { - var table = new Table(this.TableDefinitions["_SummaryInformation"]); - - for (var i = 1; 19 >= i; i++) - { - var value = summaryInformation.GetProperty(i); - - if (0 < value.Length) - { - var 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 (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) - { - foreach (var tableRecord in tablesView.Records) - { - var tableName = tableRecord.GetString(1); - - using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) - { - var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); - var table = new Table(tableDefinition); - - foreach (var rowRecord in tableView.Records) - { - var recordCount = rowRecord.GetFieldCount(); - var row = table.CreateRow(output.SourceLineNumbers); - - for (var 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: - var success = false; - var intValue = rowRecord.GetInteger(i + 1); - if (row.Fields[i].Column.IsLocalizable) - { - success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); - } - else - { - success = row.BestEffortSetField(i, intValue); - } - - if (!success) - { - this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); - } - break; - case ColumnType.Object: - var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; - - if (null != this.ExportBasePath) - { - var 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 (var fs = System.IO.File.Create(sourceFile)) - { - int bytesRead; - var buffer = new byte[512]; - - while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - this.exportedFiles.Add(sourceFile); - } - - row[i] = sourceFile; - break; - default: - var 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) - { - var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); - - if (null == modularizationGuid) - { - var match = modularization.Match(value); - 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 - // TODO: Evaluate this requirement. - //var matches = Common.WixVariableRegex.Matches(value); - //for (var 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) - { - var table = output.Tables["_SummaryInformation"]; - - foreach (var row in table.Rows) - { - if (9 == (int)row[0]) // PID_REVNUMBER - { - row[1] = modularizationGuid; - } - } - } - - if (this.IsAdminImage) - { - this.GenerateWixFileTable(this.DatabasePath, output); - this.GenerateSectionIds(output); - } - - return output; - } - - private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) - { - // Use our table definitions whenever possible since they will be used when compiling the source code anyway. - // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes. - if (this.TableDefinitions.Contains(tableName)) - { - return this.TableDefinitions[tableName]; - } - - ColumnDefinition[] columns; - using (Record columnNameRecord = tableView.GetColumnNames(), - columnTypeRecord = tableView.GetColumnTypes()) - { - // index the primary keys - var tablePrimaryKeys = new HashSet(); - using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName)) - { - var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); - - for (var i = 1; i <= primaryKeysFieldCount; i++) - { - tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); - } - } - - var columnCount = columnNameRecord.GetFieldCount(); - columns = new ColumnDefinition[columnCount]; - for (var i = 1; i <= columnCount; i++) - { - var columnName = columnNameRecord.GetString(i); - var idtType = columnTypeRecord.GetString(i); - - ColumnType columnType; - int length; - bool nullable; - - var columnCategory = ColumnCategory.Unknown; - var columnModularizeType = ColumnModularizeType.None; - var primary = tablePrimaryKeys.Contains(columnName); - int? minValue = null; - int? maxValue = null; - string keyTable = null; - int? keyColumn = null; - 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 (var validationRecord = new Record(2)) - { - validationRecord.SetString(1, tableName); - validationRecord.SetString(2, columnName); - - validationView.Execute(validationRecord); - } - - using (var validationRecord = validationView.Fetch()) - { - if (null != validationRecord) - { - var validationNullable = validationRecord.GetString(3); - minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); - maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); - keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); - keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); - category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); - set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); - description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); - - // 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[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true); - } - } - - return new TableDefinition(tableName, null, columns, false); - } - - /// - /// Generates the WixFile table based on a path to an admin image msi and an Output. - /// - /// The path to the msi database file in an admin image. - /// The Output that represents the msi database. - private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output) - { - throw new NotImplementedException(); -#if TODO_FIX_UNBINDING_FILES - var adminRootPath = Path.GetDirectoryName(databaseFile); - - var componentDirectoryIndex = new Hashtable(); - var componentTable = output.Tables["Component"]; - foreach (var row in componentTable.Rows) - { - componentDirectoryIndex.Add(row[0], row[2]); - } - - // Index full source paths for all directories - var directoryDirectoryParentIndex = new Hashtable(); - var directoryFullPathIndex = new Hashtable(); - var directorySourceNameIndex = new Hashtable(); - var directoryTable = output.Tables["Directory"]; - foreach (var row in directoryTable.Rows) - { - directoryDirectoryParentIndex.Add(row[0], row[1]); - if (null == row[1]) - { - directoryFullPathIndex.Add(row[0], adminRootPath); - } - else - { - directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); - } - } - - foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) - { - if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) - { - this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); - } - } - - var fileTable = output.Tables["File"]; - var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); - foreach (var row in fileTable.Rows) - { - var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); - wixFileRow.File = (string)row[0]; - wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; - wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); - - if (!File.Exists(wixFileRow.Source)) - { - throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); - } - - wixFileTable.Rows.Add(wixFileRow); - } -#endif - } - - /// - /// 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) - { - var parent = (string)directoryDirectoryParentIndex[directory]; - var sourceName = (string)directorySourceNameIndex[directory]; - - string parentFullPath; - if (directoryFullPathIndex.ContainsKey(parent)) - { - parentFullPath = (string)directoryFullPathIndex[parent]; - } - else - { - parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); - } - - if (null == sourceName) - { - sourceName = String.Empty; - } - - var 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 string GetAdminSourceName(string value) - { - string name = null; - string[] names; - string shortname = null; - string shortsourcename = null; - string sourcename = null; - - names = this.BackendHelper.SplitMsiFileName(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(WindowsInstallerData output) - { - // First assign and index section ids for the tables that are in their own sections. - this.AssignSectionIdsToTable(output.Tables["Binary"], 0); - var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0); - var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0); - this.AssignSectionIdsToTable(output.Tables["Directory"], 0); - var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0); - this.AssignSectionIdsToTable(output.Tables["Icon"], 0); - var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); - this.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. - var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); - var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); - var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); - var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); - var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); - var 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 (var 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 (var row in table.Rows) - { - var lockObject = (string)row[0]; - var 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) - { - var hashtable = new Hashtable(); - if (null != table) - { - foreach (var row in table.Rows) - { - row.SectionId = this.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 (var 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) - { - var newHashTable = new Hashtable(); - if (null != table) - { - foreach (var 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 deleted file mode 100644 index 75ee6307..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Unbind -{ - using System; - using System.ComponentModel; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Core.Native.Msi; - - internal class UnbindMsiOrMsmCommand - { - public UnbindMsiOrMsmCommand(IUnbindContext context) - { - this.Context = context; - } - - public IUnbindContext Context { get; } - - public Intermediate Execute() - { -#if TODO_PATCHING - 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; -#endif - throw new NotImplementedException(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs deleted file mode 100644 index f40aed4e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller.Unbind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class UnbindTransformCommand - { - public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.TransformFile = transformFile; - this.ExportBasePath = exportBasePath; - this.IntermediateFolder = intermediateFolder; - - this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private string TransformFile { get; } - - private string ExportBasePath { get; } - - private string IntermediateFolder { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private string EmptyFile { get; set; } - - public WindowsInstallerData Execute() - { - var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); - transform.Type = OutputType.Transform; - - // get the summary information table - using (var summaryInformation = new SummaryInformation(this.TransformFile)) - { - var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - - for (var i = 1; 19 >= i; i++) - { - var value = summaryInformation.GetProperty(i); - - if (0 < value.Length) - { - var row = table.CreateRow(transform.SourceLineNumbers); - row[0] = i; - row[1] = value; - } - } - } - - // create a schema msi which hopefully matches the table schemas in the transform - var schemaOutput = new WindowsInstallerData(null); - var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); - foreach (var tableDefinition in this.TableDefinitions) - { - // skip unreal tables and the Patch table - if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) - { - schemaOutput.EnsureTable(tableDefinition); - } - } - - var addedRows = new Dictionary(); - Table transformViewTable; - - // Bind the schema msi. - this.GenerateDatabase(schemaOutput, msiDatabaseFile); - - // apply the transform to the database and retrieve the modifications - using (var 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, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); - var transformViewOutput = unbindCommand.Execute(); - - // index the added and possibly modified rows (added rows may also appears as modified rows) - transformViewTable = transformViewOutput.Tables["_TransformView"]; - var modifiedRows = new Hashtable(); - foreach (var row in transformViewTable.Rows) - { - var tableName = (string)row[0]; - var columnName = (string)row[1]; - var primaryKeys = (string)row[2]; - - if ("INSERT" == columnName) - { - var index = String.Concat(tableName, ':', primaryKeys); - - addedRows.Add(index, null); - } - else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row - { - var 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) - { - var tableName = (string)row[0]; - var columnName = (string)row[1]; - var primaryKeys = (string)row[2]; - - var index = String.Concat(tableName, ':', primaryKeys); - - // ignore information for added rows - if (!addedRows.ContainsKey(index)) - { - var 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 (var 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(ErrorMessages.TransformSchemaMismatch()); - } - } - - // unbind the database - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); - var output = unbindCommand.Execute(); - - // index all the rows to easily find modified rows - var rows = new Dictionary(); - foreach (var table in output.Tables) - { - foreach (var row in table.Rows) - { - rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); - } - } - - // process the _TransformView rows into transform rows - foreach (var row in transformViewTable.Rows) - { - var tableName = (string)row[0]; - var columnName = (string)row[1]; - var primaryKeys = (string)row[2]; - - var table = transform.EnsureTable(this.TableDefinitions[tableName]); - - if ("CREATE" == columnName) // added table - { - table.Operation = TableOperation.Add; - } - else if ("DELETE" == columnName) // deleted row - { - var 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 - { - var index = String.Concat(tableName, ':', primaryKeys); - var addedRow = rows[index]; - addedRow.Operation = RowOperation.Add; - table.Rows.Add(addedRow); - } - else if (null != primaryKeys) // modified row - { - var 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.ContainsKey(index)) - { - var modifiedRow = rows[index]; - - // mark the field as modified - var indexOfModifiedValue = -1; - for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++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 - { - var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); - column.Added = true; - } - } - } - - return transform; - } - - /// - /// 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) - { - var row = table.CreateRow(null); - - var primaryKeyParts = primaryKeys.Split('\t'); - var primaryKeyPartIndex = 0; - - for (var i = 0; i < table.Definition.Columns.Length; i++) - { - var 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.Combine(this.IntermediateFolder, ".empty"); - using (var fileStream = File.Create(this.EmptyFile)) - { - } - } - - row[i] = this.EmptyFile; - } - else - { - row[i] = "1"; - } - } - } - - return row; - } - - private void GenerateDatabase(WindowsInstallerData output, string databaseFile) - { - var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); - command.Execute(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs deleted file mode 100644 index 0c15ad05..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using WixToolset.Data; - - internal static class WindowsInstallerBackendErrors - { - //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers) - //{ - // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2); - //} - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - // ReplaceThisWithTheFirstError = 7500, - } // last available is 7999. 8000 is BurnBackendErrors. - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs deleted file mode 100644 index f72acb21..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.IO; - using WixToolset.Extensibility; - - internal class WindowsInstallerBackendFactory : IBackendFactory - { - public bool TryCreateBackend(string outputType, string outputFile, 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 "package": - 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/WindowsInstallerBackendWarnings.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs deleted file mode 100644 index d0986a4d..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using WixToolset.Data; - - internal static class WindowsInstallerBackendWarnings - { - //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers) - //{ - // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2); - //} - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - // ReplaceThisWithTheFirstWarning = 7100, - } // last available is 7499. 7500 is WindowsInstallerBackendErrors. - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs deleted file mode 100644 index 7b12fc8c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using WixToolset.Extensibility; - - internal class WindowsInstallerExtensionFactory : IExtensionFactory - { - public bool TryCreateExtension(Type extensionType, out object extension) - { - extension = null; - - if (extensionType == typeof(IBackendFactory)) - { - extension = new WindowsInstallerBackendFactory(); - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj deleted file mode 100644 index b08f337f..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Core Windows Installer - WiX Toolset Core Windows Installer - embedded - true - true - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs deleted file mode 100644 index e686fa49..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.Collections.Generic; - using WixToolset.Core.WindowsInstaller.ExtensibilityServices; - using WixToolset.Extensibility.Services; - - /// - /// Extensions methods for adding WindowsInstaller services. - /// - public static class WixToolsetCoreServiceProviderExtensions - { - /// - /// Adds WindowsInstaller services. - /// - /// - /// - public static IWixToolsetCoreServiceProvider AddWindowsInstallerBackend(this IWixToolsetCoreServiceProvider coreProvider) - { - AddServices(coreProvider); - - var extensionManager = coreProvider.GetService(); - extensionManager.Add(typeof(WindowsInstallerExtensionFactory).Assembly); - - return coreProvider; - } - - private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) - { - // Singletons. - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper(provider))); - } - - private static T AddSingleton(Dictionary singletons, T service) where T : class - { - singletons.Add(typeof(T), service); - return service; - } - } -} diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs deleted file mode 100644 index 25641516..00000000 --- a/src/WixToolset.Core/Bind/DelayedField.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Bind -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - /// - /// Holds a symbol and field that contain binder variables, which need to be resolved - /// later, once the files have been resolved. - /// - internal class DelayedField : IDelayedField - { - /// - /// Creates a delayed field. - /// - /// Symbol for the field. - /// Field needing further resolution. - public DelayedField(IntermediateSymbol symbol, IntermediateField field) - { - this.Symbol = symbol; - this.Field = field; - } - - /// - /// The row containing the field. - /// - public IntermediateSymbol Symbol { get; } - - /// - /// The field needing further resolving. - /// - public IntermediateField Field { get; } - } -} diff --git a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs deleted file mode 100644 index b27cdfee..00000000 --- a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Bind -{ - using System; - using WixToolset.Extensibility.Data; - - internal class ExpectedExtractFile : IExpectedExtractFile - { - public Uri Uri { get; set; } - - public string EmbeddedFileId { get; set; } - - public string OutputPath { get; set; } - } -} diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs deleted file mode 100644 index a0798e62..00000000 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.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.Core.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - - /// - /// Internal helper class used to extract embedded files. - /// - internal class ExtractEmbeddedFiles - { - private readonly Dictionary> filesWithEmbeddedFiles = new Dictionary>(); - - 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. - /// - /// Uri to file containing the embedded files. - /// Id of the embedded file to extract. - /// Folder where extracted files should be placed. - /// The extract path for the embedded file. - public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder) - { - // If the uri to the file that contains the embedded file does not already have embedded files - // being extracted, create the dictionary to track that. - if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) - { - extracts = new SortedList(StringComparer.OrdinalIgnoreCase); - this.filesWithEmbeddedFiles.Add(uri, extracts); - } - - // If the embedded file is not already tracked in the dictionary of extracts, add it. - if (!extracts.TryGetValue(embeddedFileId, out var extractPath)) - { - var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); - var unique = this.HashUri(uri.AbsoluteUri); - var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); - - extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); - extracts.Add(embeddedFileId, extractPath); - } - - return extractPath; - } - - public IReadOnlyList GetExpectedEmbeddedFiles() - { - var files = new List(); - - foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) - { - foreach (var extracts in uriWithExtracts.Value) - { - files.Add(new ExpectedExtractFile - { - Uri = uriWithExtracts.Key, - EmbeddedFileId = extracts.Key, - OutputPath = extracts.Value, - }); - } - } - - return files; - } - - public IEnumerable GetExtractFilesForUri(Uri uri) - { - if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) - { - extracts = new SortedList(StringComparer.OrdinalIgnoreCase); - } - - return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value }); - } - - private string HashUri(string uri) - { - using (SHA1 sha1 = new SHA1CryptoServiceProvider()) - { - var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); - return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); - } - } - } -} diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs deleted file mode 100644 index ec2d8896..00000000 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.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.Core.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ExtractEmbeddedFilesCommand - { - public ExtractEmbeddedFilesCommand(IBackendHelper backendHelper, IEnumerable embeddedFiles) - { - this.BackendHelper = backendHelper; - this.FilesWithEmbeddedFiles = embeddedFiles; - } - - public IReadOnlyList TrackedFiles { get; private set; } - - private IBackendHelper BackendHelper { get; } - - private IEnumerable FilesWithEmbeddedFiles { get; } - - public void Execute() - { - var trackedFiles = new List(); - var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); - - foreach (var expectedEmbeddedFileByUri in group) - { - var baseUri = expectedEmbeddedFileByUri.Key; - - using (var wixout = WixOutput.Read(baseUri)) - { - var uniqueIds = new SortedSet(StringComparer.OrdinalIgnoreCase); - - foreach (var embeddedFile in expectedEmbeddedFileByUri) - { - if (uniqueIds.Add(embeddedFile.EmbeddedFileId)) - { - wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath); - trackedFiles.Add(this.BackendHelper.TrackFile(embeddedFile.OutputPath, TrackedFileType.Temporary)); - } - } - } - } - - this.TrackedFiles = trackedFiles; - } - } -} diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs deleted file mode 100644 index eb878239..00000000 --- a/src/WixToolset.Core/Bind/FileResolver.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class FileResolver - { - private const string BindPathOpenString = "!(bindpath."; - - private FileResolver(IEnumerable bindPaths) - { - this.BindPaths = (bindPaths ?? Array.Empty()).ToLookup(b => b.Stage); - this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); - this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); - } - - public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) - { - this.ResolverExtensions = extensions ?? Array.Empty(); - } - - public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) - { - this.LibrarianExtensions = extensions ?? Array.Empty(); - } - - private ILookup BindPaths { get; } - - public bool RebaseTarget { get; } - - public bool RebaseUpdated { get; } - - private IEnumerable ResolverExtensions { get; } - - private IEnumerable LibrarianExtensions { get; } - - public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source) - { - var checkedPaths = new List(); - - foreach (var extension in this.LibrarianExtensions) - { - var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); - } - - /// - /// Resolves the source path of a file using binder extensions. - /// - /// Original source value. - /// Optional type of source file being resolved. - /// Optional source line of source file being resolved. - /// The binding stage used to determine what collection of bind paths will be used - /// Optional collection of paths already checked. - /// Should return a valid path for the stream to be imported. - public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable alreadyCheckedPaths = null) - { - var checkedPaths = new List(); - - if (alreadyCheckedPaths != null) - { - checkedPaths.AddRange(alreadyCheckedPaths); - } - - foreach (var extension in this.ResolverExtensions) - { - var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); - } - - private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List checkedPaths) - { - string resolved = null; - - // If the file exists, we're good to go. - checkedPaths.Add(source); - if (CheckFileExists(source)) - { - resolved = source; - } - else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. - { - resolved = null; - } - else // not a rooted path so let's try applying all the different source resolution options. - { - var bindName = String.Empty; - var path = source; - var pathWithoutSourceDir = String.Empty; - - if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) - { - var closeParen = source.IndexOf(')', BindPathOpenString.Length); - - if (-1 != closeParen) - { - bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); - path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. - path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. - } - } - else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) - { - pathWithoutSourceDir = path.Substring(10); - } - - var bindPaths = this.BindPaths[bindStage]; - - foreach (var bindPath in bindPaths) - { - if (String.IsNullOrEmpty(bindName)) - { - if (String.IsNullOrEmpty(bindPath.Name)) - { - if (!String.IsNullOrEmpty(pathWithoutSourceDir)) - { - var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - - if (String.IsNullOrEmpty(resolved)) - { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - } - } - else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) - { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - - if (!String.IsNullOrEmpty(resolved)) - { - break; - } - } - } - - if (null == resolved) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); - } - - return resolved; - } - - private static bool CheckFileExists(string path) - { - try - { - return File.Exists(path); - } - catch (ArgumentException) - { - throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); - } - } - } -} diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs deleted file mode 100644 index 4ad8f764..00000000 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Text; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Resolves the fields which had variables that needed to be resolved after the file information - /// was loaded. - /// - internal class ResolveDelayedFieldsCommand - { - /// - /// Resolve delayed fields. - /// - /// - /// The fields which had resolution delayed. - /// The cached variable values used when resolving delayed fields. - public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable delayedFields, Dictionary variableCache) - { - this.Messaging = messaging; - this.DelayedFields = delayedFields; - this.VariableCache = variableCache; - } - - private IMessaging Messaging { get; } - - private IEnumerable DelayedFields { get;} - - private IDictionary VariableCache { get; } - - public void Execute() - { - var deferredFields = new List(); - - foreach (var delayedField in this.DelayedFields) - { - try - { - var propertySymbol = delayedField.Symbol; - - // process properties first in case they refer to other binder variables - if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property) - { - var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString()); - - // update the variable cache with the new value - var key = String.Concat("property.", propertySymbol.Id.Id); - this.VariableCache[key] = value; - - // update the field data - delayedField.Field.Set(value); - } - else - { - deferredFields.Add(delayedField); - } - } - catch (WixException we) - { - this.Messaging.Write(we.Error); - continue; - } - } - - // add specialization for ProductVersion fields - var keyProductVersion = "property.ProductVersion"; - if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion)) - { - // Don't add the variable if it already exists (developer defined a property with the same name). - var fieldKey = String.Concat(keyProductVersion, ".Major"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); - } - - fieldKey = String.Concat(keyProductVersion, ".Minor"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture); - } - - fieldKey = String.Concat(keyProductVersion, ".Build"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture); - } - - fieldKey = String.Concat(keyProductVersion, ".Revision"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture); - } - } - - // process the remaining fields in case they refer to property binder variables - foreach (var delayedField in deferredFields) - { - try - { - var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString()); - delayedField.Field.Set(value); - } - catch (WixException we) - { - this.Messaging.Write(we.Error); - } - } - } - - private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value) - { - var start = 0; - - while (Common.TryParseWixVariable(value, start, out var parsed)) - { - if (parsed.Namespace == "bind") - { - var key = String.Concat(parsed.Name, ".", parsed.Scope); - - if (!this.VariableCache.TryGetValue(key, out var resolvedValue)) - { - resolvedValue = parsed.DefaultValue; - } - - // insert the resolved value if it was found or display an error - if (null != resolvedValue) - { - if (parsed.Index == 0 && parsed.Length == value.Length) - { - value = resolvedValue; - } - else - { - var sb = new StringBuilder(value); - sb.Remove(parsed.Index, parsed.Length); - sb.Insert(parsed.Index, resolvedValue); - value = sb.ToString(); - } - - start = parsed.Index; - } - else - { - this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); - break; - } - } - else - { - start = parsed.Index + parsed.Length; - } - } - - return value; - } - } -} diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs deleted file mode 100644 index 794208e5..00000000 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Resolve source fields in the tables included in the output - /// - internal class ResolveFieldsCommand - { - public IMessaging Messaging { private get; set; } - - public bool BuildingPatch { private get; set; } - - public IVariableResolver VariableResolver { private get; set; } - - public IEnumerable BindPaths { private get; set; } - - public IEnumerable Extensions { private get; set; } - - public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } - - public string IntermediateFolder { private get; set; } - - public Intermediate Intermediate { private get; set; } - - public bool SupportDelayedResolution { private get; set; } - - public bool AllowUnresolvedVariables { private get; set; } - - public IReadOnlyCollection DelayedFields { get; private set; } - - public void Execute() - { - var delayedFields = this.SupportDelayedResolution ? new List() : null; - - var fileResolver = new FileResolver(this.BindPaths, this.Extensions); - - // Build the column lookup only when needed. - Dictionary customColumnsById = null; - - foreach (var sections in this.Intermediate.Sections) - { - foreach (var symbol in sections.Symbols) - { - foreach (var field in symbol.Fields) - { - if (field.IsNull()) - { - continue; - } - - var fieldType = field.Type; - - // Custom table cells require an extra look up to the column definition as the - // cell's data type is always a string (because strings can store anything) but - // the column definition may be more specific. - if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) - { - // We only care about the Data in a CustomTable cell. - if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) - { - continue; - } - - if (customColumnsById == null) - { - customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType()).ToDictionary(t => t.Id.Id); - } - - if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) - { - fieldType = customColumn.Type; - } - } - - // Check to make sure we're in a scenario where we can handle variable resolution. - if (null != delayedFields) - { - // resolve localization and wix variables - if (fieldType == IntermediateFieldType.String) - { - var original = field.AsString(); - if (!String.IsNullOrEmpty(original)) - { - var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); - if (resolution.UpdatedValue) - { - field.Set(resolution.Value); - } - - if (resolution.DelayedResolve) - { - delayedFields.Add(new DelayedField(symbol, field)); - } - } - } - } - - // Move to next symbol if we've hit an error resolving variables. - if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. - { - continue; - } - - // Resolve file paths - if (fieldType == IntermediateFieldType.Path) - { - this.ResolvePathField(fileResolver, symbol, field); - -#if TODO_PATCHING - if (null != objectField.PreviousData) - { - objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); - - if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. - { - // file is compressed in a cabinet (and not modified above) - if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) - { - // when loading transforms from disk, PreviousBaseUri may not have been set - if (null == objectField.PreviousBaseUri) - { - objectField.PreviousBaseUri = objectField.BaseUri; - } - - string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); - - // set the path to the file once its extracted from the cabinet - objectField.PreviousData = extractPath; - } - else if (null != objectField.PreviousData) // non-compressed file (or localized value) - { - try - { - if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) - { - // resolve the path to the file - objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal); - } - else - { - if (fileResolver.RebaseTarget) - { - // if -bt is used, it come here - // Try to use the original unresolved source from either target build or update build - // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here - // If target is old version and updated is new version, it uses unresolved path from updated build - // If both target and updated are of new versions, it uses unresolved path from target build - if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData) - { - objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData; - } - } - - // resolve the path to the file - objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target); - - } - } - catch (WixFileNotFoundException) - { - // display the error with source line information - Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData)); - } - } - } - } -#endif - } - } - } - } - - this.DelayedFields = delayedFields; - } - - private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) - { - var fieldValue = field.AsPath(); - var originalFieldPath = fieldValue.Path; - -#if TODO_PATCHING - // Skip file resolution if the file is to be deleted. - if (RowOperation.Delete == symbol.Operation) - { - continue; - } -#endif - - // If the file is embedded and if the previous value has a bind variable in the path - // which gets modified by resolving the previous value again then switch to that newly - // resolved path instead of using the embedded file. - if (fieldValue.Embed && field.PreviousValue != null) - { - var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false); - - if (resolution.UpdatedValue && !resolution.IsDefault) - { - fieldValue = new IntermediateFieldPathValue { Path = resolution.Value }; - } - } - - // If we're still using the embedded file. - if (fieldValue.Embed) - { - // Set the path to the embedded file once where it will be extracted. - var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(fieldValue.BaseUri, fieldValue.Path, this.IntermediateFolder); - - field.Set(extractPath); - } - else if (fieldValue.Path != null) - { - try - { - var resolvedPath = fieldValue.Path; - - if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) - { -#if TODO_PATCHING - // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file - if (null == objectField.UnresolvedData) - { - objectField.UnresolvedData = (string)objectField.Data; - } -#endif - resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); - } - else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) - { - resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); - } -#if TODO_PATCHING - else // Re-base binding path scenario caused by pyro.exe -bt -bu - { - // by default, use the resolved Data for file lookup - string filePathToResolve = (string)objectField.Data; - - // if -bu is used in pyro command, this condition holds true and the tool - // will use pre-resolved source for new wixpdb file - if (fileResolver.RebaseUpdated) - { - // try to use the unResolved Source if it exists. - // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll - // Old version of winpdb file does not contain this attribute and the value is null. - if (null != objectField.UnresolvedData) - { - filePathToResolve = objectField.UnresolvedData; - } - } - - objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated); - } -#endif - - if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) - { - field.Set(resolvedPath); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs deleted file mode 100644 index b3b74fbc..00000000 --- a/src/WixToolset.Core/Bind/TransferFilesCommand.cs +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class TransferFilesCommand - { - public TransferFilesCommand(IMessaging messaging, IEnumerable extensions, IEnumerable fileTransfers, bool resetAcls) - { - this.Extensions = extensions; - this.Messaging = messaging; - this.FileTransfers = fileTransfers; - this.ResetAcls = resetAcls; - } - - private IMessaging Messaging { get; } - - private IEnumerable Extensions { get; } - - private IEnumerable FileTransfers { get; } - - private bool ResetAcls { get; } - - public void Execute() - { - var destinationFiles = new List(); - - foreach (var fileTransfer in this.FileTransfers) - { - // If the source and destination are identical, then there's nothing to do here - if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) - { - fileTransfer.Redundant = true; - continue; - } - - var retry = false; - do - { - try - { - if (fileTransfer.Move) - { - this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination)); - this.MoveFile(fileTransfer.Source, fileTransfer.Destination); - } - else - { - this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination)); - this.CopyFile(fileTransfer.Source, fileTransfer.Destination); - } - - retry = false; - destinationFiles.Add(fileTransfer.Destination); - } - catch (FileNotFoundException e) - { - throw new WixException(ErrorMessages.FileNotFound(fileTransfer.SourceLineNumbers, e.FileName)); - } - catch (DirectoryNotFoundException) - { - // if we already retried, give up - if (retry) - { - throw; - } - - var directory = Path.GetDirectoryName(fileTransfer.Destination); - this.Messaging.Write(VerboseMessages.CreateDirectory(directory)); - Directory.CreateDirectory(directory); - retry = true; - } - catch (UnauthorizedAccessException) - { - // if we already retried, give up - if (retry) - { - throw; - } - - if (File.Exists(fileTransfer.Destination)) - { - this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); - - // try to ensure the file is not read-only - var attributes = File.GetAttributes(fileTransfer.Destination); - try - { - File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); - } - catch (ArgumentException) // thrown for unauthorized access errors - { - throw new WixException(ErrorMessages.UnauthorizedAccess(fileTransfer.Destination)); - } - - // try to delete the file - try - { - File.Delete(fileTransfer.Destination); - } - catch (IOException) - { - throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); - } - - retry = true; - } - else // no idea what just happened, bail - { - throw; - } - } - catch (IOException) - { - // if we already retried, give up - if (retry) - { - throw; - } - - if (File.Exists(fileTransfer.Destination)) - { - this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); - - // ensure the file is not read-only, then delete it - var attributes = File.GetAttributes(fileTransfer.Destination); - File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); - try - { - File.Delete(fileTransfer.Destination); - } - catch (IOException) - { - throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); - } - - retry = true; - } - else // no idea what just happened, bail - { - throw; - } - } - } while (retry); - } - - // Finally, if directed then reset remove ACLs that may may have been picked up - // during the file transfer process. - if (this.ResetAcls && 0 < destinationFiles.Count) - { - try - { - FileSystem.ResetAcls(destinationFiles); - } - catch (Exception e) - { - this.Messaging.Write(WarningMessages.UnableToResetAcls(e.Message)); - } - } - } - - private void CopyFile(string source, string destination) - { - foreach (var extension in this.Extensions) - { - if (extension.CopyFile(source, destination)) - { - return; - } - } - - FileSystem.CopyFile(source, destination, allowHardlink: true); - } - - private void MoveFile(string source, string destination) - { - foreach (var extension in this.Extensions) - { - if (extension.MoveFile(source, destination)) - { - return; - } - } - - FileSystem.MoveFile(source, destination); - } - } -} diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs deleted file mode 100644 index 052382f1..00000000 --- a/src/WixToolset.Core/BindContext.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class BindContext : IBindContext - { - internal BindContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection BindPaths { get; set; } - - public string BurnStubPath { get; set; } - - public int CabbingThreadCount { get; set; } - - public string CabCachePath { get; set; } - - public CompressionLevel? DefaultCompressionLevel { get; set; } - - public IReadOnlyCollection DelayedFields { get; set; } - - public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection FileSystemExtensions { get; set; } - - public IReadOnlyCollection Ices { get; set; } - - public string IntermediateFolder { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - - public string OutputPath { get; set; } - - public PdbType PdbType { get; set; } - - public string PdbPath { get; set; } - - public int? ResolvedCodepage { get; set; } - - public int? ResolvedSummaryInformationCodepage { get; set; } - - public int? ResolvedLcid { get; set; } - - public IReadOnlyCollection SuppressIces { get; set; } - - public bool SuppressValidation { get; set; } - - public bool SuppressLayout { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/BindFileWithPath.cs b/src/WixToolset.Core/BindFileWithPath.cs deleted file mode 100644 index 539600d3..00000000 --- a/src/WixToolset.Core/BindFileWithPath.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Data; - - /// - /// Bind file with its path. - /// - internal class BindFileWithPath : IBindFileWithPath - { - /// - /// Gets or sets the identifier of the file with this path. - /// - public string Id { get; set; } - - /// - /// Gets or sets the file path. - /// - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core/BindPath.cs b/src/WixToolset.Core/BindPath.cs deleted file mode 100644 index f70d5e36..00000000 --- a/src/WixToolset.Core/BindPath.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Diagnostics; - using WixToolset.Extensibility.Data; - - /// - /// Bind path representation. - /// - [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")] - internal class BindPath : IBindPath - { - public string Name { get; set; } - - public string Path { get; set; } - - public BindStage Stage { get; set; } - } -} diff --git a/src/WixToolset.Core/BindResult.cs b/src/WixToolset.Core/BindResult.cs deleted file mode 100644 index 9785484c..00000000 --- a/src/WixToolset.Core/BindResult.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class BindResult : IBindResult - { - private bool disposed; - - public IReadOnlyCollection FileTransfers { get; set; } - - public IReadOnlyCollection TrackedFiles { get; set; } - - public WixOutput Wixout { get; set; } - - #region IDisposable Support - /// - /// Disposes of the internal state of the file structure. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of the internsl state of the file structure. - /// - /// True if disposing. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.Wixout?.Dispose(); - } - } - - this.disposed = true; - } - #endregion - } -} diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs deleted file mode 100644 index 204ab6ee..00000000 --- a/src/WixToolset.Core/Binder.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Binder of the WiX toolset. - /// - internal class Binder : IBinder - { - internal Binder(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IBindResult Bind(IBindContext context) - { - // Prebind. - // - foreach (var extension in context.Extensions) - { - extension.PreBind(context); - } - - // Bind. - // - this.WriteBuildInfoSymbol(context.IntermediateRepresentation, context.OutputPath, context.PdbPath); - - var bindResult = this.BackendBind(context); - - if (bindResult != null) - { - // Postbind. - // - foreach (var extension in context.Extensions) - { - extension.PostBind(bindResult); - } - } - - return bindResult; - } - - private IBindResult BackendBind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendFactories = extensionManager.GetServices(); - - var entrySection = context.IntermediateRepresentation.Sections.First(); - - foreach (var factory in backendFactories) - { - if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, out var backend)) - { - var result = backend.Bind(context); - return result; - } - } - - // TODO: messaging that a backend could not be found to bind the output type? - - return null; - } - - private void WriteBuildInfoSymbol(Intermediate output, string outputFile, string outputPdbPath) - { - var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); - - var executingAssembly = Assembly.GetExecutingAssembly(); - var fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); - - var buildInfoSymbol = entrySection.AddSymbol(new WixBuildInfoSymbol() - { - WixVersion = fileVersion.FileVersion, - WixOutputFile = outputFile, - }); - - if (!String.IsNullOrEmpty(outputPdbPath)) - { - buildInfoSymbol.WixPdbFile = outputPdbPath; - } - } - } -} diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs deleted file mode 100644 index 5f618b81..00000000 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ /dev/null @@ -1,912 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BuildCommand : ICommandLineCommand - { - private readonly CommandLine commandLine; - - public BuildCommand(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.ExtensionManager = serviceProvider.GetService(); - this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging); - } - - public bool ShowLogo => this.commandLine.ShowLogo; - - public bool StopParsing => this.commandLine.ShowHelp; - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private IExtensionManager ExtensionManager { get; } - - private string IntermediateFolder { get; set; } - - private OutputType OutputType { get; set; } - - private List IncludeSearchPaths { get; set; } - - public string PdbFile { get; set; } - - public PdbType PdbType { get; set; } - - private Platform Platform { get; set; } - - private string OutputFile { get; set; } - - private CompressionLevel? DefaultCompressionLevel { get; set; } - - private string ContentsFile { get; set; } - - private string OutputsFile { get; set; } - - private string BuiltOutputsFile { get; set; } - - public Task ExecuteAsync(CancellationToken cancellationToken) - { - if (this.commandLine.ShowHelp) - { - Console.WriteLine("TODO: Show build command help"); - return Task.FromResult(-1); - } - - this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); - - this.OutputType = this.commandLine.CalculateOutputType(); - - this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths; - - this.PdbFile = this.commandLine.PdbFile; - - this.PdbType = this.commandLine.PdbType; - - this.Platform = this.commandLine.Platform; - - this.ContentsFile = this.commandLine.ContentsFile; - - this.OutputsFile = this.commandLine.OutputsFile; - - this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; - - this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel; - - var preprocessorVariables = this.commandLine.GatherPreprocessorVariables(); - - var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder); - - var filterCultures = this.commandLine.CalculateFilterCultures(); - - var creator = this.ServiceProvider.GetService(); - - this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl); - - this.OutputFile = this.commandLine.OutputFile; - - if (String.IsNullOrEmpty(this.OutputFile)) - { - if (codeFiles.Count == 1) - { - // If output type is unknown, the extension will be replaced with the right default based on output type. - this.OutputFile = Path.ChangeExtension(codeFiles[0].OutputPath, DefaultExtensionForOutputType(this.OutputType)); - } - else - { - this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput()); - } - } - - if (this.Messaging.EncounteredError) - { - return Task.FromResult(this.Messaging.LastErrorNumber); - } - - var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles, cancellationToken); - - var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables, cancellationToken); - - if (this.Messaging.EncounteredError) - { - return Task.FromResult(this.Messaging.LastErrorNumber); - } - - if (this.OutputType == OutputType.Library) - { - using (new IntermediateFieldContext("wix.lib")) - { - var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths, cancellationToken); - - if (!this.Messaging.EncounteredError) - { - wixlib.Save(this.OutputFile); - } - } - } - else - { - using (new IntermediateFieldContext("wix.link")) - { - if (wixipl == null) - { - wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator, cancellationToken); - } - - if (!this.Messaging.EncounteredError) - { - var outputExtension = Path.GetExtension(this.OutputFile); - if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension) - { - var entrySectionType = wixipl.Sections.Single().Type; - this.OutputFile = Path.ChangeExtension(this.OutputFile, DefaultExtensionForSectionType(entrySectionType)); - } - - if (this.OutputType == OutputType.IntermediatePostLink) - { - wixipl.Save(this.OutputFile); - } - else - { - using (new IntermediateFieldContext("wix.bind")) - { - this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, cancellationToken); - } - } - } - } - } - - return Task.FromResult(this.Messaging.LastErrorNumber); - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - return this.commandLine.TryParseArgument(argument, parser); - } - - private void EvaluateSourceFiles(IEnumerable sourceFiles, ISymbolDefinitionCreator creator, out List codeFiles, out Intermediate wixipl) - { - codeFiles = new List(); - - wixipl = null; - - foreach (var sourceFile in sourceFiles) - { - var extension = Path.GetExtension(sourceFile.SourcePath); - - if (wixipl != null || ".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase)) - { - codeFiles.Add(sourceFile); - } - else - { - try - { - wixipl = Intermediate.Load(sourceFile.SourcePath, creator); - } - catch (WixException) - { - // We'll assume anything that isn't a valid intermediate is source code to compile. - codeFiles.Add(sourceFile); - } - } - } - - if (wixipl == null && codeFiles.Count == 0) - { - this.Messaging.Write(ErrorMessages.NoSourceFiles()); - } - else if (wixipl != null && codeFiles.Count != 0) - { - this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive()); - } - } - - private IReadOnlyList CompilePhase(IDictionary preprocessorVariables, IEnumerable sourceFiles, CancellationToken cancellationToken) - { - var intermediates = new List(); - - foreach (var sourceFile in sourceFiles) - { - var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath, cancellationToken); - - if (this.Messaging.EncounteredError) - { - continue; - } - - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.Platform = this.Platform; - context.Source = document; - context.CancellationToken = cancellationToken; - - Intermediate intermediate = null; - try - { - var compiler = this.ServiceProvider.GetService(); - intermediate = compiler.Compile(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - if (this.Messaging.EncounteredError) - { - continue; - } - - intermediates.Add(intermediate); - } - - return intermediates; - } - - private Intermediate LibraryPhase(IReadOnlyCollection intermediates, IReadOnlyCollection localizations, bool bindFiles, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) - { - var context = this.ServiceProvider.GetService(); - context.BindFiles = bindFiles; - context.BindPaths = bindPaths; - context.Extensions = this.ExtensionManager.GetServices(); - context.Localizations = localizations; - context.Intermediates = intermediates; - context.CancellationToken = cancellationToken; - - Intermediate library = null; - try - { - var librarian = this.ServiceProvider.GetService(); - library = librarian.Combine(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - return library; - } - - private Intermediate LinkPhase(IEnumerable intermediates, IEnumerable libraryFiles, ISymbolDefinitionCreator creator, CancellationToken cancellationToken) - { - var libraries = this.LoadLibraries(libraryFiles, creator); - - if (this.Messaging.EncounteredError) - { - return null; - } - - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.ExtensionData = this.ExtensionManager.GetServices(); - context.ExpectedOutputType = this.OutputType; - context.Intermediates = intermediates.Concat(libraries).ToList(); - context.SymbolDefinitionCreator = creator; - context.CancellationToken = cancellationToken; - - var linker = this.ServiceProvider.GetService(); - return linker.Link(context); - } - - private void BindPhase(Intermediate output, IReadOnlyCollection localizations, IReadOnlyCollection filterCultures, string cabCachePath, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) - { - var intermediateFolder = this.IntermediateFolder; - if (String.IsNullOrEmpty(intermediateFolder)) - { - intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - } - - IResolveResult resolveResult; - { - var context = this.ServiceProvider.GetService(); - context.BindPaths = bindPaths; - context.Extensions = this.ExtensionManager.GetServices(); - context.ExtensionData = this.ExtensionManager.GetServices(); - context.FilterCultures = filterCultures; - context.IntermediateFolder = intermediateFolder; - context.IntermediateRepresentation = output; - context.Localizations = localizations; - context.CancellationToken = cancellationToken; - - var resolver = this.ServiceProvider.GetService(); - resolveResult = resolver.Resolve(context); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - IBindResult bindResult = null; - try - { - { - var context = this.ServiceProvider.GetService(); - //context.CabbingThreadCount = this.CabbingThreadCount; - context.CabCachePath = cabCachePath; - context.ResolvedCodepage = resolveResult.Codepage; - context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage; - context.ResolvedLcid = resolveResult.PackageLcid; - context.DefaultCompressionLevel = this.DefaultCompressionLevel; - context.DelayedFields = resolveResult.DelayedFields; - context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; - context.Extensions = this.ExtensionManager.GetServices(); - context.FileSystemExtensions = this.ExtensionManager.GetServices(); - context.Ices = this.commandLine.Ices; - context.IntermediateFolder = intermediateFolder; - context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; - context.OutputPath = this.OutputFile; - context.PdbType = this.PdbType; - context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); - context.SuppressIces = this.commandLine.SuppressIces; - context.SuppressValidation = this.commandLine.SuppressValidation; - context.CancellationToken = cancellationToken; - - var binder = this.ServiceProvider.GetService(); - bindResult = binder.Bind(context); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - { - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.TrackedFiles = bindResult.TrackedFiles; - context.FileTransfers = bindResult.FileTransfers; - context.IntermediateFolder = intermediateFolder; - context.ContentsFile = this.ContentsFile; - context.OutputsFile = this.OutputsFile; - context.BuiltOutputsFile = this.BuiltOutputsFile; - context.ResetAcls = this.commandLine.ResetAcls; - context.CancellationToken = cancellationToken; - - var layout = this.ServiceProvider.GetService(); - layout.Layout(context); - } - } - finally - { - bindResult?.Dispose(); - } - } - - private IEnumerable LoadLibraries(IEnumerable libraryFiles, ISymbolDefinitionCreator creator) - { - try - { - return Intermediate.Load(libraryFiles, creator); - } - catch (WixCorruptFileException e) - { - this.Messaging.Write(e.Error); - } - catch (WixUnexpectedFileFormatException e) - { - this.Messaging.Write(e.Error); - } - - return Array.Empty(); - } - - private IReadOnlyList LoadLocalizationFiles(IEnumerable locFiles, IDictionary preprocessorVariables, CancellationToken cancellationToken) - { - var localizations = new List(); - var parser = this.ServiceProvider.GetService(); - - foreach (var loc in locFiles) - { - var document = this.Preprocess(preprocessorVariables, loc, cancellationToken); - - if (this.Messaging.EncounteredError) - { - continue; - } - - var localization = parser.ParseLocalization(document); - localizations.Add(localization); - } - - return localizations; - } - - private XDocument Preprocess(IDictionary preprocessorVariables, string sourcePath, CancellationToken cancellationToken) - { - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.Platform = this.Platform; - context.IncludeSearchPaths = this.IncludeSearchPaths; - context.SourcePath = sourcePath; - context.Variables = preprocessorVariables; - context.CancellationToken = cancellationToken; - - IPreprocessResult result = null; - try - { - var preprocessor = this.ServiceProvider.GetService(); - result = preprocessor.Preprocess(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - return result?.Document; - } - - private static string DefaultExtensionForSectionType(SectionType sectionType) - { - switch (sectionType) - { - case SectionType.Bundle: - return ".exe"; - case SectionType.Module: - return ".msm"; - case SectionType.Product: - return ".msi"; - case SectionType.PatchCreation: - return ".pcp"; - case SectionType.Patch: - return ".msp"; - case SectionType.Fragment: - case SectionType.Unknown: - default: - return ".wix"; - } - } - - private static string DefaultExtensionForOutputType(OutputType outputType) - { - switch (outputType) - { - case OutputType.Bundle: - return ".exe"; - case OutputType.Library: - return ".wixlib"; - case OutputType.Module: - return ".msm"; - case OutputType.Patch: - return ".msp"; - case OutputType.PatchCreation: - return ".pcp"; - case OutputType.Product: - return ".msi"; - case OutputType.Transform: - return ".mst"; - case OutputType.IntermediatePostLink: - return ".wixipl"; - case OutputType.Unknown: - default: - return ".wix"; - } - } - - private class CommandLine - { - private static readonly char[] BindPathSplit = { '=' }; - - public bool BindFiles { get; private set; } - - public List BindPaths { get; } = new List(); - - public string CabCachePath { get; private set; } - - public List Cultures { get; } = new List(); - - public List Defines { get; } = new List(); - - public List IncludeSearchPaths { get; } = new List(); - - public List LocalizationFilePaths { get; } = new List(); - - public List LibraryFilePaths { get; } = new List(); - - public List SourceFilePaths { get; } = new List(); - - public Platform Platform { get; private set; } - - public string PdbFile { get; private set; } - - public PdbType PdbType { get; private set; } - - public bool ShowLogo { get; private set; } - - public bool ShowHelp { get; private set; } - - public string IntermediateFolder { get; private set; } - - public string OutputFile { get; private set; } - - public string OutputType { get; private set; } - - public CompressionLevel? DefaultCompressionLevel { get; private set; } - - public string ContentsFile { get; private set; } - - public string OutputsFile { get; private set; } - - public string BuiltOutputsFile { get; private set; } - - public List Ices { get; } = new List(); - - public List SuppressIces { get; } = new List(); - - public bool SuppressValidation { get; set; } - - public bool ResetAcls { get; set; } - - public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) - { - this.ServiceProvider = serviceProvider; - this.Messaging = messaging; - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - public bool TryParseArgument(string arg, ICommandLineParser parser) - { - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1).ToLowerInvariant(); - switch (parameter) - { - case "?": - case "h": - case "help": - this.ShowHelp = true; - return true; - - case "arch": - case "platform": - { - var value = parser.GetNextArgumentOrError(arg); - if (Enum.TryParse(value, true, out Platform platform)) - { - this.Platform = platform; - return true; - } - break; - } - - case "bf": - case "bindfiles": - this.BindFiles = true; - return true; - - case "bindpath": - { - var value = parser.GetNextArgumentOrError(arg); - if (value != null && this.TryParseBindPath(value, out var bindPath)) - { - this.BindPaths.Add(bindPath); - return true; - } - return false; - } - - case "cc": - this.CabCachePath = parser.GetNextArgumentOrError(arg); - return true; - - case "culture": - parser.GetNextArgumentOrError(arg, this.Cultures); - return true; - - case "contentsfile": - this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "outputsfile": - this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "builtoutputsfile": - this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "d": - case "define": - parser.GetNextArgumentOrError(arg, this.Defines); - return true; - - case "dcl": - case "defaultcompressionlevel": - { - var value = parser.GetNextArgumentOrError(arg); - if (Enum.TryParse(value, true, out CompressionLevel compressionLevel)) - { - this.DefaultCompressionLevel = compressionLevel; - return true; - } - return false; - } - - case "i": - case "includepath": - parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); - return true; - - case "ice": - { - var value = parser.GetNextArgumentOrError(arg); - this.Ices.Add(value); - return true; - } - - case "intermediatefolder": - this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); - return true; - - case "loc": - parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths); - return true; - - case "lib": - parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths); - return true; - - case "o": - case "out": - this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "outputtype": - this.OutputType = parser.GetNextArgumentOrError(arg); - return true; - - case "pdb": - this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "pdbtype": - { - var value = parser.GetNextArgumentOrError(arg); - if (Enum.TryParse(value, true, out PdbType pdbType)) - { - this.PdbType = pdbType; - return true; - } - return false; - } - - case "sice": - { - var value = parser.GetNextArgumentOrError(arg); - this.SuppressIces.Add(value); - return true; - } - - case "nologo": - this.ShowLogo = false; - return true; - - case "v": - case "verbose": - this.Messaging.ShowVerboseMessages = true; - return true; - - case "sval": - this.SuppressValidation = true; - return true; - - case "resetacls": - this.ResetAcls = true; - return true; - } - - if (parameter.StartsWith("sw")) - { - this.ParseSuppressWarning(parameter, "sw".Length, parser); - return true; - } - else if (parameter.StartsWith("suppresswarning")) - { - this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); - return true; - } - else if (parameter.StartsWith("wx")) - { - this.ParseWarningAsError(parameter, "wx".Length, parser); - return true; - } - - return false; - } - else - { - parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths); - return true; - } - } - - public string CalculateIntermedateFolder() - { - return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; - } - - public OutputType CalculateOutputType() - { - if (String.IsNullOrEmpty(this.OutputType)) - { - this.OutputType = Path.GetExtension(this.OutputFile); - } - - switch (this.OutputType?.ToLowerInvariant()) - { - case "bundle": - case ".exe": - return Data.OutputType.Bundle; - - case "library": - case ".wixlib": - return Data.OutputType.Library; - - case "module": - case ".msm": - return Data.OutputType.Module; - - case "patch": - case ".msp": - return Data.OutputType.Patch; - - case ".pcp": - return Data.OutputType.PatchCreation; - - case "product": - case "package": - case ".msi": - return Data.OutputType.Product; - - case "transform": - case ".mst": - return Data.OutputType.Transform; - - case "intermediatepostlink": - case ".wixipl": - return Data.OutputType.IntermediatePostLink; - } - - return Data.OutputType.Unknown; - } - - public IReadOnlyList CalculateFilterCultures() - { - var result = new List(); - - if (this.Cultures == null) - { - } - else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) - { - // When null is used treat it as if cultures wasn't specified. This is - // needed for batching in the MSBuild task since MSBuild doesn't support - // empty items. - } - else - { - foreach (var culture in this.Cultures) - { - // Neutral is different from null. For neutral we still want to do culture filtering. - // Set the culture to the empty string = identifier for the invariant culture. - var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; - result.Add(filter); - } - } - - return result; - } - - public IDictionary GatherPreprocessorVariables() - { - var variables = new Dictionary(); - - foreach (var pair in this.Defines) - { - var value = pair.Split(new[] { '=' }, 2); - - if (variables.ContainsKey(value[0])) - { - this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); - continue; - } - - variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); - } - - return variables; - } - - public IEnumerable GatherSourceFiles(string intermediateDirectory) - { - var files = new List(); - - foreach (var item in this.SourceFilePaths) - { - var sourcePath = item; - var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); - - files.Add(new SourceFile(sourcePath, outputPath)); - } - - return files; - } - - private bool TryParseBindPath(string bindPath, out IBindPath bp) - { - var namedPath = bindPath.Split(BindPathSplit, 2); - - bp = this.ServiceProvider.GetService(); - - if (1 == namedPath.Length) - { - bp.Path = namedPath[0]; - } - else - { - bp.Name = namedPath[0]; - bp.Path = namedPath[1]; - } - - if (File.Exists(bp.Path)) - { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); - return false; - } - - return true; - } - - private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.SuppressAllWarnings = true; - } - else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) - { - this.Messaging.SuppressWarningMessage(suppressWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); - } - } - - private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.WarningsAsError = true; - } - else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) - { - this.Messaging.ElevateWarningMessage(elevateWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); - } - } - } - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs deleted file mode 100644 index b87b6a5d..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLine.cs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal enum CommandTypes - { - Unknown, - Build, - Preprocess, - Compile, - Link, - Bind, - Decompile, - } - - internal class CommandLine : ICommandLine - { - public CommandLine(IServiceProvider serviceProvider) => this.ServiceProvider = serviceProvider; - - private IServiceProvider ServiceProvider { get; } - - public ICommandLineCommand CreateCommand(string[] args) - { - var arguments = this.ServiceProvider.GetService(); - arguments.Populate(args); - - this.LoadExtensions(arguments.Extensions); - - return this.ParseStandardCommandLine(arguments); - } - - public ICommandLineCommand CreateCommand(string commandLine) - { - var arguments = this.ServiceProvider.GetService(); - arguments.Populate(commandLine); - - this.LoadExtensions(arguments.Extensions); - - return this.ParseStandardCommandLine(arguments); - } - - public ICommandLineCommand ParseStandardCommandLine(ICommandLineArguments arguments) - { - var context = this.ServiceProvider.GetService(); - context.ExtensionManager = this.ServiceProvider.GetService(); - context.Arguments = arguments; - - var command = this.Parse(context); - - if (command.ShowLogo) - { - var branding = this.ServiceProvider.GetService(); - Console.WriteLine(branding.ReplacePlaceholders("[AssemblyProduct] [AssemblyDescription] version [FileVersion]")); - Console.WriteLine(branding.ReplacePlaceholders("[AssemblyCopyright]")); - } - - return command; - } - - private void LoadExtensions(string[] extensions) - { - var extensionManager = this.ServiceProvider.GetService(); - - foreach (var extension in extensions) - { - extensionManager.Load(extension); - } - } - - private ICommandLineCommand Parse(ICommandLineContext context) - { - var branding = context.ServiceProvider.GetService(); - var extensions = context.ExtensionManager.GetServices(); - - foreach (var extension in extensions) - { - extension.PreParse(context); - } - - ICommandLineCommand command = null; - var parser = context.Arguments.Parse(); - - while (command?.StopParsing != true && - String.IsNullOrEmpty(parser.ErrorArgument) && - parser.TryGetNextSwitchOrArgument(out var arg)) - { - if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. - { - continue; - } - - // First argument must be the command or global switch (that creates a command). - if (command == null) - { - if (!this.TryParseCommand(arg, parser, extensions, out command)) - { - parser.ReportErrorArgument(arg); - } - } - else if (parser.IsSwitch(arg)) - { - if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) - { - parser.ReportErrorArgument(arg); - } - } - else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && !command.TryParseArgument(parser, arg)) - { - parser.ReportErrorArgument(arg); - } - } - - foreach (var extension in extensions) - { - extension.PostParse(); - } - - return command ?? new HelpCommand(extensions, branding); - } - - private bool TryParseCommand(string arg, ICommandLineParser parser, IEnumerable extensions, out ICommandLineCommand command) - { - command = null; - - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "help": - case "-help": - var branding = this.ServiceProvider.GetService(); - command = new HelpCommand(extensions, branding); - break; - - case "version": - case "-version": - command = new VersionCommand(); - break; - } - } - else - { - if (Enum.TryParse(arg, true, out CommandTypes commandType)) - { - switch (commandType) - { - case CommandTypes.Build: - command = new BuildCommand(this.ServiceProvider); - break; - - case CommandTypes.Compile: - command = new CompileCommand(this.ServiceProvider); - break; - - case CommandTypes.Decompile: - command = new DecompileCommand(this.ServiceProvider); - break; - } - } - else - { - foreach (var extension in extensions) - { - if (extension.TryParseCommand(parser, arg, out command)) - { - break; - } - - command = null; - } - } - } - - return command != null; - } - - private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable extensions) - { - foreach (var extension in extensions) - { - if (extension.TryParseArgument(parse, arg)) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs deleted file mode 100644 index 40b8b320..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CommandLineArguments : ICommandLineArguments - { - public CommandLineArguments(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - public string[] OriginalArguments { get; set; } - - public string[] Arguments { get; set; } - - public string[] Extensions { get; set; } - - public string ErrorArgument { get; set; } - - private IMessaging Messaging { get; } - - public void Populate(string commandLine) - { - var args = CommandLineArguments.ParseArgumentsToArray(commandLine); - - this.Populate(args.ToArray()); - } - - public void Populate(string[] args) - { - this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args); - - this.ProcessArgumentsAndParseExtensions(this.OriginalArguments); - } - - public ICommandLineParser Parse() => new CommandLineParser(this.Messaging, this.Arguments, this.ErrorArgument); - - private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) - { - var args = new List(); - - foreach (var arg in commandLineArguments) - { - if (arg != null) - { - if ('@' == arg[0]) - { - var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1)); - args.AddRange(responseFileArguments); - } - else - { - args.Add(arg); - } - } - } - - this.OriginalArguments = args.ToArray(); - } - - private void ProcessArgumentsAndParseExtensions(string[] args) - { - var arguments = new List(); - var extensions = new List(); - - for (var i = 0; i < args.Length; ++i) - { - var arg = args[i]; - - if ("-ext" == arg || "/ext" == arg) - { - if (!CommandLineArguments.IsSwitchAt(args, ++i)) - { - extensions.Add(args[i]); - } - else - { - this.ErrorArgument = arg; - break; - } - } - else - { - arguments.Add(arg); - } - } - - this.Arguments = arguments.ToArray(); - this.Extensions = extensions.ToArray(); - } - - private static List ParseResponseFile(string responseFile) - { - string arguments; - - using (var reader = new StreamReader(responseFile)) - { - arguments = reader.ReadToEnd(); - } - - return CommandLineArguments.ParseArgumentsToArray(arguments); - } - - private static List ParseArgumentsToArray(string arguments) - { - // Scan and parse the arguments string, dividing up the arguments based on whitespace. - // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed. - // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments. - // Escaped quotes and escaped backslashes also need to be unescaped by this process. - - // Collects the final list of arguments to be returned. - var argsList = new List(); - - // True if we are inside an unescaped quote, meaning whitespace should be ignored. - var insideQuote = false; - - // Index of the start of the current argument substring; either the start of the argument - // or the start of a quoted or unquoted sequence within it. - var partStart = 0; - - // The current argument string being built; when completed it will be added to the list. - var arg = new StringBuilder(); - - for (var i = 0; i <= arguments.Length; i++) - { - if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote)) - { - // Reached a whitespace separator or the end of the string. - - // Finish building the current argument. - arg.Append(arguments.Substring(partStart, i - partStart)); - - // Skip over the whitespace character. - partStart = i + 1; - - // Add the argument to the list if it's not empty. - if (arg.Length > 0) - { - argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString())); - arg.Length = 0; - } - } - else if (i > partStart && arguments[i - 1] == '\\') - { - // Check the character following an unprocessed backslash. - // Unescape quotes, and backslashes followed by a quote. - if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"')) - { - // Unescape the quote or backslash by skipping the preceeding backslash. - arg.Append(arguments.Substring(partStart, i - 1 - partStart)); - arg.Append(arguments[i]); - partStart = i + 1; - } - } - else if (arguments[i] == '"') - { - // Add the quoted or unquoted section to the argument string. - arg.Append(arguments.Substring(partStart, i - partStart)); - - // And skip over the quote character. - partStart = i + 1; - - insideQuote = !insideQuote; - } - } - - return argsList; - } - - private static string ExpandEnvironmentVariables(string arguments) - { - var id = Environment.GetEnvironmentVariables(); - - var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)"); - var matches = regex.Matches(arguments); - - var value = String.Empty; - for (var i = 0; i <= (matches.Count - 1); i++) - { - try - { - var key = matches[i].Value; - regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)")); - value = id[key].ToString(); - arguments = regex.Replace(arguments, value); - } - catch (NullReferenceException) - { - // Collapse unresolved environment variables. - arguments = regex.Replace(arguments, value); - } - } - - return arguments; - } - - private static bool IsSwitchAt(string[] args, int index) => args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]); - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/WixToolset.Core/CommandLine/CommandLineContext.cs deleted file mode 100644 index 8d5cf120..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineContext.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CommandLineContext : ICommandLineContext - { - public CommandLineContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IExtensionManager ExtensionManager { get; set; } - - public ICommandLineArguments Arguments { get; set; } - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs deleted file mode 100644 index 015d3e62..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineParser.cs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class CommandLineParser : ICommandLineParser - { - private const string ExpectedArgument = "expected argument"; - - public string ErrorArgument { get; private set; } - - private Queue RemainingArguments { get; } - - private IMessaging Messaging { get; } - - public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument) - { - this.Messaging = messaging; - this.RemainingArguments = new Queue(arguments); - this.ErrorArgument = errorArgument; - } - - public bool IsSwitch(string arg) - { - return !String.IsNullOrEmpty(arg) && '-' == arg[0]; - } - - public string GetArgumentAsFilePathOrError(string argument, string fileType) - { - if (!File.Exists(argument)) - { - this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType)); - return null; - } - - return argument; - } - - public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) - { - foreach (var path in this.GetFiles(argument, fileType)) - { - paths.Add(path); - } - } - - public string GetNextArgumentOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) - { - return argument; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentOrError(string commandLineSwitch, IList args) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) - { - args.Add(arg); - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) - { - return directory; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) - { - directories.Add(directory); - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) - { - return path; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) - { - foreach (var path in this.GetFiles(arg, fileType)) - { - paths.Add(path); - } - - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public void ReportErrorArgument(string argument, Message message = null) - { - this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument)); - this.ErrorArgument = argument; - } - - public bool TryGetNextSwitchOrArgument(out string arg) - { - if (this.RemainingArguments.Count > 0) - { - arg = this.RemainingArguments.Dequeue(); - return true; - } - - arg = null; - return false; - } - - private bool TryGetNextNonSwitchArgumentOrError(out string arg) - { - var result = this.TryGetNextSwitchOrArgument(out arg); - - if (!result || this.IsSwitch(arg)) - { - this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; - return false; - } - - return result; - } - - private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory) - { - directory = null; - - if (File.Exists(arg)) - { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); - return false; - } - - directory = this.VerifyPath(arg); - return directory != null; - } - - private bool TryGetFile(string commandlineSwitch, string arg, out string path) - { - path = null; - - if (String.IsNullOrEmpty(arg) || '-' == arg[0]) - { - this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); - } - else if (Directory.Exists(arg)) - { - this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); - } - else - { - path = this.VerifyPath(arg); - } - - return path != null; - } - - /// - /// Get a set of files that possibly have a search pattern in the path (such as '*'). - /// - /// Search path to find files in. - /// Type of file; typically "Source". - /// An array of files matching the search path. - /// - /// This method is written in this verbose way because it needs to support ".." in the path. - /// It needs the directory path isolated from the file name in order to use Directory.GetFiles - /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since - /// Path.GetDirectoryName does not support ".." in the path. - /// - private string[] GetFiles(string searchPath, string fileType) - { - if (null == searchPath) - { - throw new ArgumentNullException(nameof(searchPath)); - } - - // Convert alternate directory separators to the standard one. - var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); - var files = new string[0]; - - try - { - if (0 > lastSeparator) - { - files = Directory.GetFiles(".", filePath); - } - else // found directory separator - { - files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); - } - } - catch (DirectoryNotFoundException) - { - // Don't let this function throw the DirectoryNotFoundException. This exception - // occurs for non-existant directories and invalid characters in the searchPattern. - } - catch (ArgumentException) - { - // Don't let this function throw the ArgumentException. This exception - // occurs in certain situations such as when passing a malformed UNC path. - } - catch (IOException) - { - } - - if (0 == files.Length) - { - this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); - } - - return files; - } - - private string VerifyPath(string path) - { - string fullPath; - - if (0 <= path.IndexOf('\"')) - { - this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); - return null; - } - - try - { - fullPath = Path.GetFullPath(path); - } - catch (Exception e) - { - this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); - return null; - } - - return fullPath; - } - } -} diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs deleted file mode 100644 index 6e31b241..00000000 --- a/src/WixToolset.Core/CommandLine/CompileCommand.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CompileCommand : ICommandLineCommand - { - public CompileCommand(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.ExtensionManager = serviceProvider.GetService(); - } - - public CompileCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, Platform platform) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.ExtensionManager = serviceProvider.GetService(); - this.SourceFiles = sources; - this.PreprocessorVariables = preprocessorVariables; - this.Platform = platform; - } - - private IServiceProvider ServiceProvider { get; } - - public IMessaging Messaging { get; } - - public IExtensionManager ExtensionManager { get; } - - private IEnumerable SourceFiles { get; } - - private IDictionary PreprocessorVariables { get; } - - private Platform Platform { get; } - - public IReadOnlyCollection IncludeSearchPaths { get; } - - public bool ShowLogo => throw new NotImplementedException(); - - public bool StopParsing => throw new NotImplementedException(); - - public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => throw new NotImplementedException(); - - public Task ExecuteAsync(CancellationToken _) - { - foreach (var sourceFile in this.SourceFiles) - { - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.Platform = this.Platform; - context.IncludeSearchPaths = this.IncludeSearchPaths; - context.SourcePath = sourceFile.SourcePath; - context.Variables = this.PreprocessorVariables; - - IPreprocessResult result = null; - try - { - var preprocessor = this.ServiceProvider.GetService(); - result = preprocessor.Preprocess(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - if (this.Messaging.EncounteredError) - { - continue; - } - - var compileContext = this.ServiceProvider.GetService(); - compileContext.Extensions = this.ExtensionManager.GetServices(); - compileContext.Platform = this.Platform; - compileContext.Source = result?.Document; - - var compiler = this.ServiceProvider.GetService(); - var intermediate = compiler.Compile(compileContext); - - intermediate.Save(sourceFile.OutputPath); - } - - return Task.FromResult(0); - } - } -} diff --git a/src/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/WixToolset.Core/CommandLine/DecompileCommand.cs deleted file mode 100644 index fc0ab0c9..00000000 --- a/src/WixToolset.Core/CommandLine/DecompileCommand.cs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class DecompileCommand : ICommandLineCommand - { - private readonly CommandLine commandLine; - - public DecompileCommand(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.commandLine = new CommandLine(this.Messaging); - } - - public bool ShowLogo => this.commandLine.ShowLogo; - - public bool StopParsing => this.commandLine.ShowHelp; - - private IServiceProvider ServiceProvider { get; } - - public IMessaging Messaging { get; } - - public Task ExecuteAsync(CancellationToken _) - { - if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath)) - { - Console.WriteLine("TODO: Show decompile command help"); - return Task.FromResult(-1); - } - - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ServiceProvider.GetService().GetServices(); - context.DecompilePath = this.commandLine.DecompileFilePath; - context.DecompileType = this.commandLine.CalculateDecompileType(); - context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); - context.OutputPath = this.commandLine.CalculateOutputPath(); - - try - { - var decompiler = this.ServiceProvider.GetService(); - var result = decompiler.Decompile(context); - - if (!this.Messaging.EncounteredError) - { - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath))); - result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - if (this.Messaging.EncounteredError) - { - return Task.FromResult(1); - } - - return Task.FromResult(0); - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - return this.commandLine.TryParseArgument(argument, parser); - } - - private class CommandLine - { - public CommandLine(IMessaging messaging) - { - this.Messaging = messaging; - } - - private IMessaging Messaging { get; } - - public string DecompileFilePath { get; private set; } - - public string DecompileType { get; private set; } - - public Platform Platform { get; private set; } - - public bool ShowLogo { get; private set; } - - public bool ShowHelp { get; private set; } - - public string IntermediateFolder { get; private set; } - - public string OutputFile { get; private set; } - - public bool TryParseArgument(string arg, ICommandLineParser parser) - { - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "help": - this.ShowHelp = true; - return true; - - case "intermediatefolder": - this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); - return true; - - case "o": - case "out": - this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "nologo": - this.ShowLogo = false; - return true; - - case "v": - case "verbose": - this.Messaging.ShowVerboseMessages = true; - return true; - } - - if (parameter.StartsWith("sw")) - { - this.ParseSuppressWarning(parameter, "sw".Length, parser); - return true; - } - else if (parameter.StartsWith("suppresswarning")) - { - this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); - return true; - } - else if (parameter.StartsWith("wx")) - { - this.ParseWarningAsError(parameter, "wx".Length, parser); - return true; - } - } - else - { - if (String.IsNullOrEmpty(this.DecompileFilePath)) - { - this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file"); - return true; - } - else if (String.IsNullOrEmpty(this.OutputFile)) - { - this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file"); - return true; - } - } - - return false; - } - - public OutputType CalculateDecompileType() - { - if (String.IsNullOrEmpty(this.DecompileType)) - { - this.DecompileType = Path.GetExtension(this.DecompileFilePath); - } - - switch (this.DecompileType.ToLowerInvariant()) - { - case "bundle": - case ".exe": - return OutputType.Bundle; - - case "library": - case ".wixlib": - return OutputType.Library; - - case "module": - case ".msm": - return OutputType.Module; - - case "patch": - case ".msp": - return OutputType.Patch; - - case ".pcp": - return OutputType.PatchCreation; - - case "product": - case "package": - case ".msi": - return OutputType.Product; - - case "transform": - case ".mst": - return OutputType.Transform; - - case "intermediatepostlink": - case ".wixipl": - return OutputType.IntermediatePostLink; - } - - return OutputType.Unknown; - } - - public string CalculateIntermedateFolder() - { - return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; - } - - public string CalculateOutputPath() - { - return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile; - } - - private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.SuppressAllWarnings = true; - } - else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) - { - this.Messaging.SuppressWarningMessage(suppressWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); - } - } - - private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.WarningsAsError = true; - } - else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) - { - this.Messaging.ElevateWarningMessage(elevateWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); - } - } - } - } -} diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs deleted file mode 100644 index 6a5ac183..00000000 --- a/src/WixToolset.Core/CommandLine/HelpCommand.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class HelpCommand : ICommandLineCommand - { - private static readonly ExtensionCommandLineSwitch[] BuiltInSwitches = new ExtensionCommandLineSwitch[] - { - new ExtensionCommandLineSwitch { Switch = "build", Description = "Build a wixlib, package or bundle." }, - new ExtensionCommandLineSwitch { Switch = "decompile", Description = "Decompile a package or bundle into source code." }, - }; - - public HelpCommand(IEnumerable extensions, IWixBranding branding) - { - this.Extensions = extensions; - this.Branding = branding; - } - - public bool ShowLogo => true; - - public bool StopParsing => true; - - private IEnumerable Extensions { get; } - - private IWixBranding Branding { get; } - - public Task ExecuteAsync(CancellationToken _) - { - var commandLineSwitches = new List(BuiltInSwitches); - commandLineSwitches.AddRange(this.Extensions.SelectMany(e => e.CommandLineSwitches).OrderBy(s => s.Switch, StringComparer.Ordinal)); - - Console.WriteLine(); - Console.WriteLine("Usage: wix [option]"); - Console.WriteLine("Usage: wix [command]"); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine(" -h|--help Show command line help."); - Console.WriteLine(" --version Display WiX Toolset version in use."); - Console.WriteLine(); - - Console.WriteLine("Commands:"); - foreach (var commandLineSwitch in commandLineSwitches) - { - Console.WriteLine(" {0,-17} {1}", commandLineSwitch.Switch, commandLineSwitch.Description); - } - - Console.WriteLine(); - Console.WriteLine("Run 'wix [command] --help' for more information on a command."); - Console.WriteLine(); - Console.WriteLine(this.Branding.ReplacePlaceholders("For more information see: [SupportUrl]")); - - return Task.FromResult(-1); - } - - public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments - } -} diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs deleted file mode 100644 index 01a7d0e6..00000000 --- a/src/WixToolset.Core/CommandLine/VersionCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.CommandLine -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class VersionCommand : ICommandLineCommand - { - public bool ShowLogo => true; - - public bool StopParsing => true; - - public Task ExecuteAsync(CancellationToken cancellationToken) - { - Console.WriteLine(ThisAssembly.AssemblyInformationalVersion); - - return Task.FromResult(0); - } - - public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments - } -} diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs deleted file mode 100644 index 848f009a..00000000 --- a/src/WixToolset.Core/Common.cs +++ /dev/null @@ -1,832 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Common Wix utility methods and types. - /// - internal static class Common - { - private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; - private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; - - internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * " - internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' - internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' - - public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler) - { - const string root = @"C:\"; - if (!Path.IsPathRooted(relativePath)) - { - var normalizedPath = Path.GetFullPath(root + relativePath); - if (normalizedPath.StartsWith(root)) - { - var canonicalizedPath = normalizedPath.Substring(root.Length); - if (canonicalizedPath != relativePath) - { - messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath)); - } - return canonicalizedPath; - } - } - - messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath)); - return relativePath; - } - - /// - /// Gets a valid code page from the given web name or integer value. - /// - /// A code page web name or integer value as a string. - /// Whether to allow -1 which does not change the database code pages. This may be the case with wxl files. - /// Whether to allow Unicode (UCS) or UTF code pages. - /// Source line information for the current authoring. - /// A valid code page number. - /// The value is an integer less than 0 or greater than 65535. - /// is null. - /// The value doesn't not represent a valid code page name or integer value. - /// The code page is invalid for summary information. - public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - try - { - Encoding encoding; - - // Check if a integer as a string was passed. - if (Int32.TryParse(value, out var codePage)) - { - if (0 == codePage) - { - // 0 represents a neutral database - return 0; - } - else if (allowNoChange && -1 == codePage) - { - // -1 means no change to the database code page - return -1; - } - - encoding = Encoding.GetEncoding(codePage); - } - else - { - encoding = Encoding.GetEncoding(value); - } - - // Windows Installer parses some code page references - // as unsigned shorts which fail to open the database. - if (onlyAnsi) - { - codePage = encoding.CodePage; - if (0 > codePage || Int16.MaxValue < codePage) - { - throw new WixException(ErrorMessages.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage)); - } - } - - if (encoding == null) - { - throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage)); - } - - return encoding.CodePage; - } - catch (ArgumentException ex) - { - // Rethrow as NotSupportedException since either can be thrown - // if the system does not support the specified code page. - throw new NotSupportedException(ex.Message, ex); - } - } - - /// - /// Verifies if an identifier is a valid binder variable name. - /// - /// Binder variable name to verify. - /// True if the identifier is a valid binder variable name. - public static bool IsValidBinderVariable(string variable) - { - return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); - } - - /// - /// Verifies if a string contains a valid binder variable name. - /// - /// String to verify. - /// True if the string contains a valid binder variable name. - public static bool ContainsValidBinderVariable(string verify) - { - return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); - } - - /// - /// Verifies the given string is a valid 4-part version module or bundle version. - /// - /// The version to verify. - /// True if version is a valid module or bundle version. - public static bool IsValidFourPartVersion(string version) - { - if (!Common.IsValidBinderVariable(version)) - { - if (!Version.TryParse(version, out var ver) || 65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision) - { - return false; - } - } - - return true; - } - - public static bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - else if (filename.Length > 259) - { - return false; - } - - // Check for a non-period character (all periods is not legal) - var allPeriods = true; - foreach (var character in filename) - { - if ('.' != character) - { - allPeriods = false; - break; - } - } - - if (allPeriods) - { - return false; - } - - if (allowWildcards) - { - return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1; - } - else if (allowRelative) - { - return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1; - } - else - { - return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1; - } - } - - public static bool IsValidShortFilename(string filename, bool allowWildcards) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - - if (allowWildcards) - { - var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters); - if (expectedDot == -1) - { - } - else if (filename[expectedDot] != '.') - { - return false; - } - else if (expectedDot < filename.Length) - { - var extensionInvalids = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters, expectedDot + 1); - if (extensionInvalids != -1) - { - return false; - } - } - - var foundPeriod = false; - var beforePeriod = 0; - var afterPeriod = 0; - - // count the number of characters before and after the period - // '*' is not counted because it may represent zero characters - foreach (var character in filename) - { - if ('.' == character) - { - foundPeriod = true; - } - else if ('*' != character) - { - if (foundPeriod) - { - afterPeriod++; - } - else - { - beforePeriod++; - } - } - } - - if (8 >= beforePeriod && 3 >= afterPeriod) - { - return true; - } - - return false; - } - else - { - if (filename.Length > 12) - { - return false; - } - - var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters); - if (expectedDot == -1) - { - return filename.Length < 9; - } - else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length) - { - return false; - } - - var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); - return validExtension == -1; - } - } - - /// - /// Generate a new Windows Installer-friendly guid. - /// - /// A new guid. - public static string GenerateGuid() - { - return Guid.NewGuid().ToString("B").ToUpperInvariant(); - } - - /// - /// Generate an identifier by hashing data from the row. - /// - /// Three letter or less prefix for generated row identifier. - /// Information to hash. - /// The generated identifier. - public static string GenerateIdentifier(string prefix, params string[] args) - { - string base64; - - using (var sha1 = new SHA1CryptoServiceProvider()) - { - var combined = String.Join("|", args); - var data = Encoding.UTF8.GetBytes(combined); - var hash = sha1.ComputeHash(data); - base64 = Convert.ToBase64String(hash); - } - - var identifier = new StringBuilder(32); - identifier.Append(prefix); - identifier.Append(base64); - identifier.Length -= 1; // removes the trailing '=' from base64 - identifier.Replace('+', '.'); - identifier.Replace('/', '_'); - - return identifier.ToString(); - } - - /// - /// Return an identifier based on provided file or directory name - /// - /// File/directory name to generate identifer from - /// A version of the name that is a legal identifier. - internal static string GetIdentifierFromName(string name) - { - StringBuilder sb = null; - var offset = 0; - - // MSI identifiers must begin with an alphabetic character or an - // underscore. Prefix all other values with an underscore. - if (!ValidIdentifierChar(name[0], true)) - { - sb = new StringBuilder("_" + name); - offset = 1; - } - - for (var i = 0; i < name.Length; ++i) - { - if (!ValidIdentifierChar(name[i], false)) - { - if (sb == null) - { - sb = new StringBuilder(name); - } - - sb[i + offset] = '_'; - } - } - - return sb?.ToString() ?? name; - } - - /// - /// Checks if the string contains a property (i.e. "foo[Property]bar") - /// - /// String to evaluate for properties. - /// True if a property is found in the string. - internal static bool ContainsProperty(string possibleProperty) - { - var start = possibleProperty.IndexOf('['); - if (start != -1 && start < possibleProperty.Length - 2) - { - var end = possibleProperty.IndexOf(']', start + 1); - if (end > start + 1) - { - // Skip supported property modifiers. - if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!') - { - ++start; - } - - var id = possibleProperty.Substring(start + 1, end - 1); - - if (Common.IsIdentifier(id)) - { - return true; - } - } - } - - return false; - } - - /// - /// Recursively loops through a directory, changing an attribute on all of the underlying files. - /// An example is to add/remove the ReadOnly flag from each file. - /// - /// The directory path to start deleting from. - /// The FileAttribute to change on each file. - /// The message handler. - /// If true, add the attribute to each file. If false, remove it. - private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessaging messageHandler) - { - foreach (var subDirectory in Directory.GetDirectories(path)) - { - RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler); - } - - foreach (var filePath in Directory.GetFiles(path)) - { - var attributes = File.GetAttributes(filePath); - if (markAttribute) - { - attributes = attributes | fileAttribute; // add to list of attributes - } - else if (fileAttribute == (attributes & fileAttribute)) // if attribute set - { - attributes = attributes ^ fileAttribute; // remove from list of attributes - } - - try - { - File.SetAttributes(filePath, attributes); - } - catch (UnauthorizedAccessException) - { - messageHandler.Write(WarningMessages.AccessDeniedForSettingAttributes(null, filePath)); - } - } - } - - /// - /// 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. - /// Thus the returned array will always be of length 4. - /// - public static string[] GetNames(string value) - { - var targetSeparator = value.IndexOf(':'); - - // split source and target - string sourceName = null; - var 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) - { - var sourceLongNameSeparator = sourceName.IndexOf('|'); - if (0 <= sourceLongNameSeparator) - { - sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); - sourceName = sourceName.Substring(0, sourceLongNameSeparator); - } - } - - // split the target short and long names - var targetLongNameSeparator = targetName.IndexOf('|'); - 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 short 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) - { - targetName = null; - } - - if ("." == targetLongName) - { - targetLongName = null; - } - - // source name(s) - if ("." == sourceName) - { - sourceName = null; - } - - if ("." == sourceLongName) - { - sourceLongName = null; - } - - return new[] { targetName, targetLongName, sourceName, sourceLongName }; - } - - /// - /// 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) - { - var 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. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. - /// The attribute's value. - internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) - { - var value = attribute.Value; - - if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || - (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) - { - messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - return String.Empty; - } - - return value; - } - - /// - /// Verifies that a value is a legal identifier. - /// - /// The value to verify. - /// true if the value is an identifier; false otherwise. - public static bool IsIdentifier(string value) - { - if (String.IsNullOrEmpty(value)) - { - return false; - } - - for (var i = 0; i < value.Length; ++i) - { - if (!ValidIdentifierChar(value[i], i == 0)) - { - return false; - } - } - - return true; - } - - /// - /// Get an identifier attribute value and displays an error for an illegal identifier value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's identifier value or a special value if an error occurred. - internal static string GetAttributeIdentifierValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); - - if (Common.IsIdentifier(value)) - { - if (72 < value.Length) - { - messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return value; - } - else - { - if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal)) - { - messaging.Write(ErrorMessages.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else - { - messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return String.Empty; - } - } - - /// - /// Get an integer attribute value and displays an error for an illegal integer value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's integer value or a special value if an error occurred during conversion. - public static int GetAttributeIntegerValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); - - var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); - var integer = CompilerConstants.IllegalInteger; - - if (0 < value.Length) - { - if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer)) - { - if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) - { - messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); - } - else if (minimum > integer || maximum < integer) - { - messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); - integer = CompilerConstants.IllegalInteger; - } - } - else - { - messaging.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return integer; - } - - /// - /// Gets a yes/no value and displays an error for an illegal yes/no value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's YesNoType value. - internal static YesNoType GetAttributeYesNoValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); - var yesNo = YesNoType.IllegalValue; - - if ("yes".Equals(value) || "true".Equals(value)) - { - yesNo = YesNoType.Yes; - } - else if ("no".Equals(value) || "false".Equals(value)) - { - yesNo = YesNoType.No; - } - else - { - messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return yesNo; - } - - /// - /// Gets the text of an XElement. - /// - /// Element to get text. - /// The element's text. - internal static string GetInnerText(XElement node) - { - var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast().FirstOrDefault(); - return text?.Value; - } - - internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable) - { - parsedVariable = null; - - if (String.IsNullOrEmpty(value) || start >= value.Length) - { - return false; - } - - var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal); - if (startWixVariable == -1) - { - return false; - } - - var firstDot = value.IndexOf('.', startWixVariable + 1); - if (firstDot == -1) - { - return false; - } - - var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2); - if (ns != "loc" && ns != "bind" && ns != "wix") - { - return false; - } - - var closeParen = value.IndexOf(')', firstDot); - if (closeParen == -1) - { - return false; - } - - string name; - string scope = null; - string defaultValue = null; - - var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); - var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; - var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot); - - if (secondDot == -1) - { - name = value.Substring(firstDot + 1, end - firstDot - 1); - } - else - { - name = value.Substring(firstDot + 1, secondDot - firstDot - 1); - scope = value.Substring(secondDot + 1, end - secondDot - 1); - - if (!Common.IsIdentifier(scope)) - { - return false; - } - } - - if (!Common.IsIdentifier(name)) - { - return false; - } - - if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) - { - defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1); - } - - parsedVariable = new ParsedWixVariable - { - Index = startWixVariable, - Length = closeParen - startWixVariable + 1, - Namespace = ns, - Name = name, - Scope = scope, - DefaultValue = defaultValue - }; - - return true; - } - - /// - /// Display an unexpected attribute error. - /// - /// - /// Source line information about the owner element. - /// The attribute. - public static void UnexpectedAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - // Ignore elements defined by the W3C because we'll assume they are always right. - if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || - attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) - { - messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - } - } - - /// - /// Display an unsupported extension attribute error. - /// - /// - /// Source line information about the owner element. - /// The extension attribute. - internal static void UnsupportedExtensionAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute) - { - // Ignore elements defined by the W3C because we'll assume they are always right. - if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || - extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) - { - messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); - } - } - - private static bool ValidIdentifierChar(char c, bool firstChar) - { - return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c || - (!firstChar && (Char.IsDigit(c) || '.' == c)); - } - } -} diff --git a/src/WixToolset.Core/Compile/CompilerPayload.cs b/src/WixToolset.Core/Compile/CompilerPayload.cs deleted file mode 100644 index 3f423034..00000000 --- a/src/WixToolset.Core/Compile/CompilerPayload.cs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - - internal class CompilerPayload - { - public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; - - public string Description { get; set; } - - public string DownloadUrl { get; set; } - - public string Hash { get; set; } - - public Identifier Id { get; set; } - - public bool IsRemoteAllowed { get; set; } - - public bool IsRequired { get; set; } = true; - - public string Name { get; set; } - - public string ProductName { get; set; } - - public long? Size { get; set; } - - public string SourceFile { get; set; } - - public string Version { get; set; } - - public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element) - { - this.Core = core; - this.Element = element; - this.SourceLineNumbers = sourceLineNumbers; - } - - private CompilerCore Core { get; } - - private XElement Element { get; } - - private SourceLineNumber SourceLineNumbers { get; } - - private void CalculateAndVerifyFields() - { - var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); - - if (String.IsNullOrEmpty(this.SourceFile)) - { - if (!String.IsNullOrEmpty(this.Name) && !isRemote) - { - this.SourceFile = Path.Combine("SourceDir", this.Name); - } - } - else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - if (String.IsNullOrEmpty(this.Name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile)); - } - else - { - this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name)); - } - } - - if (String.IsNullOrEmpty(this.SourceFile) && !isRemote) - { - if (this.IsRequired) - { - if (!this.IsRemoteAllowed) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); - } - } - } - else if (this.IsRemoteAllowed) - { - var isLocal = !String.IsNullOrEmpty(this.SourceFile); - - if (isLocal) - { - if (isRemote) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); - } - - if (!String.IsNullOrEmpty(this.Description)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Description", "SourceFile")); - } - - if (!String.IsNullOrEmpty(this.ProductName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "ProductName", "SourceFile")); - } - - if (this.Size.HasValue) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile")); - } - - if (!String.IsNullOrEmpty(this.Version)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Version", "SourceFile")); - } - } - else - { - if (String.IsNullOrEmpty(this.DownloadUrl)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); - } - - if (String.IsNullOrEmpty(this.Name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); - } - - if (!this.Size.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); - } - - if (YesNoDefaultType.Yes == this.Compressed) - { - this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); - } - - this.Compressed = YesNoDefaultType.No; - } - } - } - - public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null) - { - WixBundlePayloadSymbol symbol = null; - - if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName) - { - if (this.Compressed == YesNoDefaultType.No) - { - this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile)); - } - - if (!String.IsNullOrEmpty(this.DownloadUrl)) - { - this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id)); - } - - this.Compressed = YesNoDefaultType.Yes; - this.DownloadUrl = null; - } - - if (!this.Core.EncounteredError) - { - symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id) - { - Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name, - SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile }, - DownloadUrl = this.DownloadUrl, - Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, - UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. - DisplayName = this.ProductName, - Description = this.Description, - Hash = this.Hash, - FileSize = this.Size, - Version = this.Version, - }); - - this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId); - } - - return symbol; - } - - public void FinishCompilingPackage() - { - this.CalculateAndVerifyFields(); - this.GenerateIdFromFilename(); - - if (this.Id == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id")); - this.Id = Identifier.Invalid; - } - } - - public void FinishCompilingPackagePayload() - { - this.CalculateAndVerifyFields(); - this.GenerateIdFromFilename(); - this.GenerateIdFromPrefix("ppy"); - } - - public void FinishCompilingPayload() - { - this.CalculateAndVerifyFields(); - this.GenerateIdFromPrefix("pay"); - } - - private void GenerateIdFromFilename() - { - if (this.Id == null) - { - if (!String.IsNullOrEmpty(this.Name)) - { - this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name)); - } - else if (!String.IsNullOrEmpty(this.SourceFile)) - { - this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile)); - } - } - } - - private void GenerateIdFromPrefix(string prefix) - { - if (this.Id == null) - { - this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty); - } - } - - public void ParseCompressed(XAttribute attrib) - { - this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib); - } - - public void ParseDescription(XAttribute attrib) - { - this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseDownloadUrl(XAttribute attrib) - { - this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseHash(XAttribute attrib) - { - this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseId(XAttribute attrib) - { - this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib); - } - - public void ParseName(XAttribute attrib) - { - this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true); - if (!this.Core.IsValidLongFilename(this.Name, false, true)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name)); - } - } - - public void ParseProductName(XAttribute attrib) - { - this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseSize(XAttribute attrib) - { - this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue); - } - - public void ParseSourceFile(XAttribute attrib) - { - this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseVersion(XAttribute attrib) - { - this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - } -} diff --git a/src/WixToolset.Core/CompileContext.cs b/src/WixToolset.Core/CompileContext.cs deleted file mode 100644 index d84d7aac..00000000 --- a/src/WixToolset.Core/CompileContext.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class CompileContext : ICompileContext - { - internal CompileContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public string CompilationId { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public Platform Platform { get; set; } - - public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64; - - public XDocument Source { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs deleted file mode 100644 index c39bec70..00000000 --- a/src/WixToolset.Core/Compiler.cs +++ /dev/null @@ -1,8514 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB - private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - - private const char ComponentIdPlaceholderStart = (char)167; - private const char ComponentIdPlaceholderEnd = (char)167; - private Dictionary componentIdPlaceholders; - - // If these are true you know you are building a module or product - // but if they are false you cannot not be sure they will not end - // up a product or module. Use these flags carefully. - private bool compilingModule; - private bool compilingProduct; - - private string activeName; - private string activeLanguage; - - /// - /// Type of RadioButton element in a group. - /// - private enum RadioButtonType - { - /// Not set, yet. - NotSet, - - /// Text - Text, - - /// Bitmap - Bitmap, - - /// Icon - Icon, - } - - internal Compiler(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - public IMessaging Messaging { get; } - - private ICompileContext Context { get; set; } - - private CompilerCore Core { get; set; } - - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public Platform CurrentPlatform => this.Context.Platform; - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages { get; set; } - - /// - /// Compiles the provided Xml document into an intermediate object - /// - /// Intermediate object representing compiled source document. - /// This method is not thread-safe. - public Intermediate Compile(ICompileContext context) - { - var target = new Intermediate(); - - if (String.IsNullOrEmpty(context.CompilationId)) - { - context.CompilationId = target.Id; - } - - this.Context = context; - - var extensionsByNamespace = new Dictionary(); - - foreach (var extension in this.Context.Extensions) - { - if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension)) - { - extensionsByNamespace.Add(extension.Namespace, extension); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString())); - } - - extension.PreCompile(this.Context); - } - - // Try to compile it. - try - { - var parseHelper = this.Context.ServiceProvider.GetService(); - - this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); - this.Core.ShowPedanticMessages = this.ShowPedanticMessages; - this.componentIdPlaceholders = new Dictionary(); - - // parse the document - var source = this.Context.Source; - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root); - if ("Wix" == source.Root.Name.LocalName) - { - if (CompilerCore.WixNamespace == source.Root.Name.Namespace) - { - this.ParseWixElement(source.Root); - } - else // invalid or missing namespace - { - if (String.IsNullOrEmpty(source.Root.Name.NamespaceName)) - { - this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString())); - } - else - { - this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString())); - } - } - } - else - { - this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix")); - } - - // Resolve any Component Id placeholders compiled into the intermediate. - this.ResolveComponentIdPlaceholders(target); - } - finally - { - foreach (var extension in this.Context.Extensions) - { - extension.PostCompile(target); - } - - this.Core = null; - } - - target.UpdateLevel(Data.IntermediateLevels.Compiled); - - return this.Messaging.EncounteredError ? null : target; - } - - /// - /// Parses a Wix element. - /// - /// Element to parse. - private void ParseWixElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string requiredVersion = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "RequiredVersion": - requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != requiredVersion) - { - this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Bundle": - this.ParseBundleElement(child); - break; - case "Fragment": - this.ParseFragmentElement(child); - break; - case "Module": - this.ParseModuleElement(child); - break; - case "PatchCreation": - this.ParsePatchCreationElement(child); - break; - case "Package": - this.ParsePackageElement(child); - break; - case "Patch": - this.ParsePatchElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - private void ResolveComponentIdPlaceholders(Intermediate target) - { - if (0 < this.componentIdPlaceholders.Count) - { - foreach (var section in target.Sections) - { - foreach (var symbol in section.Symbols) - { - foreach (var field in symbol.Fields) - { - if (field != null && field.Type == IntermediateFieldType.String) - { - var data = field.AsString(); - if (!String.IsNullOrEmpty(data)) - { - var changed = false; - var start = data.IndexOf(ComponentIdPlaceholderStart); - while (start != -1) - { - var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1); - if (end == -1) - { - break; - } - - var placeholderId = data.Substring(start, end - start + 1); - if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value)) - { - var sb = new StringBuilder(data); - sb.Remove(start, end - start + 1); - sb.Insert(start, value); - - data = sb.ToString(); - changed = true; - - end = start + value.Length; - } - - start = data.IndexOf(ComponentIdPlaceholderStart, end); - } - - if (changed) - { - field.Overwrite(data); - } - } - } - } - } - } - } - } - - /// - /// Uppercases the first character of a string. - /// - /// String to uppercase first character of. - /// String with first character uppercased. - private static string UppercaseFirstChar(string s) - { - if (0 == s.Length) - { - return s; - } - - return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1)); - } - - /// - /// Lowercases the string if present. - /// - /// String to lowercase. - /// Null if the string is null, otherwise returns the lowercase. - private static string LowercaseOrNull(string s) - { - return s?.ToLowerInvariant(); - } - - /// - /// Adds a search property to the active section. - /// - /// Current source/line number of processing. - /// Property to add to search. - /// Signature for search. - private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature) - { - if (!this.Core.EncounteredError) - { - if (propertyId.Id != propertyId.Id.ToUpperInvariant()) - { - this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id)); - } - - this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature)) - { - PropertyRef = propertyId.Id, - SignatureRef = signature - }); - } - } - - /// - /// Adds a property to the active section. - /// - /// Current source/line number of processing. - /// Identifier of property to add. - /// Value of property. - /// Flag if property is an admin property. - /// Flag if property is a secure property. - /// Flag if property is to be hidden. - /// Adds the property to a new section. - private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment) - { - // properties without a valid identifier should not be processed any further - if (null == propertyId || String.IsNullOrEmpty(propertyId.Id)) - { - return; - } - - if (!String.IsNullOrEmpty(value)) - { - var start = value.IndexOf('['); - while (start != -1 && start < value.Length) - { - var end = value.IndexOf(']', start + 1); - if (end == -1) - { - break; - } - - var id = value.Substring(start + 1, end - 1); - if (Common.IsIdentifier(id)) - { - this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id)); - } - - start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1; - } - } - - if (!this.Core.EncounteredError) - { - var section = this.Core.ActiveSection; - - // Add the symbol to a separate section if requested. - if (fragment) - { - var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id); - - section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId); - - // Reference the property in the active section. - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id); - } - - // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements - // the linker will remove these symbols before the final output is created. - section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId) - { - Value = value, - }); - - if (admin || hidden || secure) - { - this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section); - } - } - } - - private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null) - { - if (secure && property.Id != property.Id.ToUpperInvariant()) - { - this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id)); - } - - if (null == section) - { - section = this.Core.ActiveSection; - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table. - } - - section.AddSymbol(new WixPropertySymbol(sourceLineNumbers) - { - PropertyRef = property.Id, - Admin = admin, - Hidden = hidden, - Secure = secure - }); - } - - /// - /// Adds a "implemented category" registry key to active section. - /// - /// Current source/line number of processing. - /// GUID for category. - /// ClassId for to mark "implemented". - /// Identifier of parent component. - private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId); - } - - /// - /// Parses an application identifer element. - /// - /// Element to parse. - /// Identifier of parent component. - /// The required advertise state (set depending upon the parent). - /// Optional file identifier for CLSID when not advertised. - /// Optional TypeLib GUID for CLSID. - /// Optional TypeLib Version for CLSID Interfaces (if any). - private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string appId = null; - string remoteServerName = null; - string localService = null; - string serviceParameters = null; - string dllSurrogate = null; - bool? activateAtStorage = null; - var appIdAdvertise = YesNoType.NotSet; - bool? runAsInteractiveUser = null; - string description = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "ActivateAtStorage": - activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Advertise": - appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DllSurrogate": - dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "LocalService": - localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "RemoteServerName": - remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "RunAsInteractiveUser": - runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ServiceParameters": - serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == appId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise)) - { - this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString())); - } - else - { - advertise = appIdAdvertise; - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Class": - this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (YesNoType.Yes == advertise) - { - if (null != description) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId)) - { - AppId = appId, - RemoteServerName = remoteServerName, - LocalService = localService, - ServiceParameters = serviceParameters, - DllSurrogate = dllSurrogate, - ActivateAtStorage = activateAtStorage, - RunAsInteractiveUser = runAsInteractiveUser, - }); - } - } - else if (YesNoType.No == advertise) - { - if (null != description) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId); - } - else - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId); - } - - if (null != remoteServerName) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId); - } - - if (null != localService) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId); - } - - if (null != serviceParameters) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId); - } - - if (null != dllSurrogate) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId); - } - - if (true == activateAtStorage) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId); - } - - if (true == runAsInteractiveUser) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId); - } - } - } - - /// - /// Parses an AssemblyName element. - /// - /// File element to parse. - /// Parent's component id. - private void ParseAssemblyName(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id)) - { - ComponentRef = componentId, - Name = id, - Value = value, - }); - } - } - - /// - /// Parses a binary element. - /// - /// Element to parse. - /// Identifier for the new row. - private Identifier ParseBinaryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - var suppressModularization = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!String.IsNullOrEmpty(id.Id)) // only check legal values - { - if (55 < id.Id.Length) - { - this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55)); - } - else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized - { - if (18 < id.Id.Length) - { - this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18)); - } - } - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) - { - Data = new IntermediateFieldPathValue { Path = sourceFile } - }); - - if (YesNoType.Yes == suppressModularization) - { - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) - { - SuppressIdentifier = id.Id - }); - } - } - - return id; - } - - /// - /// Parses an icon element. - /// - /// Element to parse. - /// Identifier for the new row. - private string ParseIconElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!String.IsNullOrEmpty(id.Id)) // only check legal values - { - if (57 < id.Id.Length) - { - this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57)); - } - else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized - { - if (20 < id.Id.Length) - { - this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20)); - } - } - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id) - { - Data = new IntermediateFieldPathValue { Path = sourceFile }, - }); - } - - return id.Id; - } - - /// - /// Parses an InstanceTransforms element. - /// - /// Element to parse. - private void ParseInstanceTransformsElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string property = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - // find unexpected child elements - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Instance": - this.ParseInstanceElement(child, property); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses an instance element. - /// - /// Element to parse. - /// Identifier of instance property. - private void ParseInstanceElement(XElement node, string propertyId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string productCode = null; - string productName = null; - string upgradeCode = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "ProductName": - productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == productCode) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id) - { - PropertyId = propertyId, - ProductCode = productCode, - ProductName = productName, - UpgradeCode = upgradeCode - }); - } - } - - /// - /// Parses a category element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseCategoryElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string appData = null; - string feature = null; - string qualifier = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "AppData": - appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); - break; - case "Qualifier": - qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == qualifier) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers) - { - ComponentId = id, - Qualifier = qualifier, - ComponentRef = componentId, - AppData = appData, - FeatureRef = feature ?? Guid.Empty.ToString("B"), - }); - } - } - - /// - /// Parses a class element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional Advertise State for the parent AppId element (if any). - /// Optional file identifier for CLSID when not advertised. - /// Optional TypeLib GUID for CLSID. - /// Optional TypeLib Version for CLSID Interfaces (if any). - /// Optional parent AppId. - private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string appId = null; - string argument = null; - var class16bit = false; - var class32bit = false; - string classId = null; - var classAdvertise = YesNoType.NotSet; - var contexts = new string[0]; - string formattedContextString = null; - var control = false; - string defaultInprocHandler = null; - string defaultProgId = null; - string description = null; - string fileTypeMask = null; - string foreignServer = null; - string icon = null; - var iconIndex = CompilerConstants.IntegerNotSet; - string insertable = null; - string localFileServer = null; - var programmable = false; - var relativePath = YesNoType.NotSet; - var safeForInit = false; - var safeForScripting = false; - var shortServerPath = false; - string threadingModel = null; - string version = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Advertise": - classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "AppId": - appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Argument": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Context": - contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); - break; - case "Control": - control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Handler": - defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "RelativePath": - relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - - // The following attributes result in rows always added to the Registry table rather than the Class table - case "Insertable": - insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable"; - break; - case "Programmable": - programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SafeForInitializing": - safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SafeForScripting": - safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ForeignServer": - foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Server": - localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortPath": - shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ThreadingModel": - threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Version": - version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == classId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - var uniqueContexts = new HashSet(); - foreach (var context in contexts) - { - if (uniqueContexts.Contains(context)) - { - this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context)); - } - else - { - uniqueContexts.Add(context); - } - - if (context.EndsWith("32", StringComparison.Ordinal)) - { - class32bit = true; - } - else - { - class16bit = true; - } - } - - if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString())); - } - else - { - advertise = classAdvertise; - } - - // If the advertise state has not been set, default to non-advertised. - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - if (YesNoType.Yes == advertise && 0 == contexts.Length) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes")); - } - - if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName)); - } - - if (!String.IsNullOrEmpty(localFileServer)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer); - } - - // Local variables used strictly for child node processing. - var fileTypeMaskIndex = 0; - var firstProgIdForClass = YesNoType.Yes; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "FileTypeMask": - if (YesNoType.Yes == advertise) - { - fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child)); - } - else if (YesNoType.No == advertise) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.CreateRegistryRow(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId); - fileTypeMaskIndex++; - } - break; - case "Interface": - this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion); - break; - case "ProgId": - { - var foundExtension = false; - var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass); - if (null == defaultProgId) - { - defaultProgId = progId; - } - firstProgIdForClass = YesNoType.No; - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // If this Class is being advertised. - if (YesNoType.Yes == advertise) - { - if (null != fileServer || null != localFileServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes")); - } - - if (null != foreignServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes")); - } - - if (null == appId && null != parentAppId) - { - appId = parentAppId; - } - - // add a Class row for each context - if (!this.Core.EncounteredError) - { - foreach (var context in contexts) - { - var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers) - { - CLSID = classId, - Context = context, - ComponentRef = componentId, - DefaultProgIdRef = defaultProgId, - Description = description, - FileTypeMask = fileTypeMask, - DefInprocHandler = defaultInprocHandler, - Argument = argument, - FeatureRef = Guid.Empty.ToString("B"), - RelativePath = YesNoType.Yes == relativePath, - }); - - if (null != appId) - { - symbol.AppIdRef = appId; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId); - } - - if (null != icon) - { - symbol.IconRef = icon; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - } - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - symbol.IconIndex = iconIndex; - } - } - } - } - else if (YesNoType.No == advertise) - { - if (null == fileServer && null == localFileServer && null == foreignServer) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); - } - - if (null != fileServer && null != foreignServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File")); - } - else if (null != localFileServer && null != foreignServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); - } - else if (null == fileServer) - { - fileServer = localFileServer; - } - - if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no")); - } - - // add the core registry keys for each context in the class - foreach (var context in contexts) - { - if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server - { - if (null != argument) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context)); - } - - if (null != fileServer) - { - formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]"); - } - else if (null != foreignServer) - { - formattedContextString = foreignServer; - } - } - else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path) - { - if (null != fileServer) - { - if (shortServerPath) - { - formattedContextString = String.Concat("[!", fileServer, "]"); - } - else - { - formattedContextString = String.Concat("\"[#", fileServer, "]\""); - } - } - else if (null != foreignServer) - { - formattedContextString = foreignServer; - } - - if (null != argument) - { - formattedContextString = String.Concat(formattedContextString, " ", argument); - } - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32")); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context - - if (null != icon) // ClassId default icon - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); - - icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - icon = String.Concat(icon, ",", iconIndex); - } - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId); - } - } - - if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId); - } - - if (null != description) // ClassId description - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId); - } - - if (null != defaultInprocHandler) - { - switch (defaultInprocHandler) // ClassId Default Inproc Handler - { - case "1": - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); - break; - case "2": - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); - break; - case "3": - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); - break; - default: - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId); - break; - } - } - - if (YesNoType.NotSet != relativePath) // ClassId's RelativePath - { - this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers)); - } - } - - if (null != threadingModel) - { - threadingModel = Compiler.UppercaseFirstChar(threadingModel); - - // add a threading model for each context in the class - foreach (var context in contexts) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId); - } - } - - if (null != typeLibId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId); - } - - if (null != version) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId); - } - - if (null != insertable) - { - // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId); - } - - if (control) - { - // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId); - } - - if (programmable) - { - // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId); - } - - if (safeForInit) - { - this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); - } - - if (safeForScripting) - { - this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); - } - } - - /// - /// Parses an Interface element. - /// - /// Element to parse. - /// Identifier of parent component. - /// 16-bit proxy for interface. - /// 32-bit proxy for interface. - /// Optional TypeLib GUID for CLSID. - /// Version of the TypeLib to which this interface belongs. Required if typeLibId is specified - private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string baseInterface = null; - string interfaceId = null; - string name = null; - var numMethods = CompilerConstants.IntegerNotSet; - var versioned = true; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "BaseInterface": - baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "NumMethods": - numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "ProxyStubClassId": - proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib); - break; - case "ProxyStubClassId32": - proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Versioned": - versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == interfaceId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId); - if (null != typeLibId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId); - if (versioned) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId); - } - } - - if (null != baseInterface) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId); - } - - if (CompilerConstants.IntegerNotSet != numMethods) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId); - } - - if (null != proxyId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId); - } - - if (null != proxyId32) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId); - } - } - - /// - /// Parses a CLSID's file type mask element. - /// - /// Element to parse. - /// String representing the file type mask elements. - private string ParseFileTypeMaskElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var cb = 0; - var offset = CompilerConstants.IntegerNotSet; - string mask = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Mask": - mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Offset": - offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - - if (null == mask) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask")); - } - - if (CompilerConstants.IntegerNotSet == offset) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (mask.Length != value.Length) - { - this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers)); - } - cb = mask.Length / 2; - } - - return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value); - } - - /// - /// Parses a product search element. - /// - /// Element to parse. - /// - /// Signature for search element. - private void ParseProductSearchElement(XElement node, string propertyId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string upgradeCode = null; - string language = null; - string maximum = null; - string minimum = null; - var excludeLanguages = false; - var maxInclusive = false; - var minInclusive = true; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludeLanguages": - excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMaximum": - maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": - minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Language": - language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == minimum && null == maximum) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - VersionMin = minimum, - VersionMax = maximum, - Language = language, - ActionProperty = propertyId, - OnlyDetect = true, - ExcludeLanguages = excludeLanguages, - VersionMaxInclusive = maxInclusive, - VersionMinInclusive = minInclusive, - }); - } - } - - /// - /// Parses a registry search element. - /// - /// Element to parse. - /// Signature for search element. - private string ParseRegistrySearchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string name = null; - RegistryRootType? root = null; - RegLocatorType? type = null; - var search64bit = this.Context.IsCurrentPlatform64Bit; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - search64bit = false; - break; - case "always64": - search64bit = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "directory": - type = RegLocatorType.Directory; - break; - case "file": - type = RegLocatorType.FileName; - break; - case "raw": - type = RegLocatorType.Raw; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString()); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (!type.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); - } - - var signature = id.Id; - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - - // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures - id = new Identifier(AccessModifier.Section, newId); - signature = null; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Type = type.Value, - Win64 = search64bit, - }); - } - - return signature; - } - - /// - /// Parses a registry search reference element. - /// - /// Element to parse. - /// Signature of referenced search element. - private string ParseRegistrySearchRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; // the id of the RegistrySearchRef element is its signature - } - - /// - /// Parses child elements for search signatures. - /// - /// Node whose children we are parsing. - /// Returns list of string signatures. - private List ParseSearchSignatures(XElement node) - { - var signatures = new List(); - - foreach (var child in node.Elements()) - { - string signature = null; - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComplianceDrive": - signature = this.ParseComplianceDriveElement(child); - break; - case "ComponentSearch": - signature = this.ParseComponentSearchElement(child); - break; - case "DirectorySearch": - signature = this.ParseDirectorySearchElement(child, String.Empty); - break; - case "DirectorySearchRef": - signature = this.ParseDirectorySearchRefElement(child, String.Empty); - break; - case "IniFileSearch": - signature = this.ParseIniFileSearchElement(child); - break; - case "ProductSearch": - // handled in ParsePropertyElement - break; - case "RegistrySearch": - signature = this.ParseRegistrySearchElement(child); - break; - case "RegistrySearchRef": - signature = this.ParseRegistrySearchRefElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - - - if (!String.IsNullOrEmpty(signature)) - { - signatures.Add(signature); - } - } - - return signatures; - } - - /// - /// Parses a compliance drive element. - /// - /// Element to parse. - /// Signature of nested search elements. - private string ParseComplianceDriveElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string signature = null; - - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE"); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == signature) - { - this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); - } - - return signature; - } - - /// - /// Parses a compilance check element. - /// - /// Element to parse. - private void ParseComplianceCheckElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - string signature = null; - - // see if this property is used for appSearch - var signatures = this.ParseSearchSignatures(node); - foreach (var sig in signatures) - { - // if we haven't picked a signature for this ComplianceCheck pick - // this one - if (null == signature) - { - signature = sig; - } - else if (signature != sig) - { - // all signatures under a ComplianceCheck must be the same - this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature)); - } - } - - if (null == signature) - { - this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature))); - } - } - - /// - /// Parses a component element. - /// - /// Element to parse. - /// Type of component's complex reference parent. Will be Uknown if there is no parent. - /// Optional identifier for component's primary parent. - /// Optional string for component's parent's language. - /// Optional disk id inherited from parent directory. - /// Optional identifier for component's directory. - /// Optional source path for files up to this point. - private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - var comPlusBits = CompilerConstants.IntegerNotSet; - string condition = null; - string subdirectory = null; - var encounteredODBCDataSource = false; - var files = 0; - var guid = "*"; - Identifier id = null; - string componentIdPlaceholder = null; - var keyFound = false; - string keyPath = null; - - var keyPathType = ComponentKeyPathType.Directory; - var location = ComponentLocation.LocalOnly; - var disableRegistryReflection = false; - - var neverOverwrite = false; - var permanent = false; - var shared = false; - var sharedDllRefCount = false; - var transitive = false; - var uninstallWhenSuperseded = false; - var win64 = this.Context.IsCurrentPlatform64Bit; - - var multiInstance = false; - var symbols = new List(); - string feature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - win64 = false; - break; - case "always64": - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "ComPlusFlags": - comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "DisableRegistryReflection": - disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Guid": - guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true); - break; - case "KeyPath": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - keyFound = true; - keyPath = null; - } - break; - case "Location": - var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (locationValue) - { - case "either": - location = ComponentLocation.Either; - break; - case "local": // this is the default - location = ComponentLocation.LocalOnly; - break; - case "source": - location = ComponentLocation.SourceOnly; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source")); - break; - } - break; - case "MultiInstance": - multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "NeverOverwrite": - neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Permanent": - permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Shared": - shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SharedDllRefCount": - sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Transitive": - transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "UninstallWhenSuperseded": - uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (id == null) - { - // Placeholder id for defaulting Component/@Id to keypath id. - componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd); - id = new Identifier(AccessModifier.Section, componentIdPlaceholder); - } - - if (String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); - } - - if (!String.IsNullOrEmpty(subdirectory)) - { - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); - } - - if (String.IsNullOrEmpty(guid) && shared) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", "")); - } - - if (String.IsNullOrEmpty(guid) && permanent) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", "")); - } - - if (null != feature) - { - if (this.compilingModule) - { - this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature")); - } - else - { - if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName)); - } - else - { - this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); - } - } - } - - foreach (var child in node.Elements()) - { - var keyPathSet = YesNoType.NotSet; - string keyPossible = null; - ComponentKeyPathType? keyBit = null; - - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null); - break; - case "Category": - this.ParseCategoryElement(child, id.Id); - break; - case "Class": - this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null); - break; - case "CopyFile": - this.ParseCopyFileElement(child, id.Id, null); - break; - case "CreateFolder": - var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64); - break; - case "Environment": - this.ParseEnvironmentElement(child, id.Id); - break; - case "Extension": - this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null); - break; - case "File": - keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid); - keyBit = ComponentKeyPathType.File; - files++; - break; - case "IniFile": - this.ParseIniFileElement(child, id.Id); - break; - case "Interface": - this.ParseInterfaceElement(child, id.Id, null, null, null, null); - break; - case "IsolateComponent": - this.ParseIsolateComponentElement(child, id.Id); - break; - case "ODBCDataSource": - keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible); - keyBit = ComponentKeyPathType.OdbcDataSource; - encounteredODBCDataSource = true; - break; - case "ODBCDriver": - this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver); - break; - case "ODBCTranslator": - this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator); - break; - case "ProgId": - var foundExtension = false; - this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet); - break; - case "Provides": - if (win64) - { - this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id)); - } - - keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible); - keyBit = ComponentKeyPathType.Registry; - break; - - case "RegistryKey": - keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible); - keyBit = ComponentKeyPathType.Registry; - break; - case "RegistryValue": - keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible); - keyBit = ComponentKeyPathType.Registry; - break; - case "RemoveFile": - this.ParseRemoveFileElement(child, id.Id, directoryId); - break; - case "RemoveFolder": - this.ParseRemoveFolderElement(child, id.Id, directoryId); - break; - case "RemoveRegistryKey": - this.ParseRemoveRegistryKeyElement(child, id.Id); - break; - case "RemoveRegistryValue": - this.ParseRemoveRegistryValueElement(child, id.Id); - break; - case "ReserveCost": - this.ParseReserveCostElement(child, id.Id, directoryId); - break; - case "ServiceConfig": - this.ParseServiceConfigElement(child, id.Id, null); - break; - case "ServiceConfigFailureActions": - this.ParseServiceConfigFailureActionsElement(child, id.Id, null); - break; - case "ServiceControl": - this.ParseServiceControlElement(child, id.Id); - break; - case "ServiceInstall": - this.ParseServiceInstallElement(child, id.Id, win64); - break; - case "Shortcut": - this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No); - break; - case "SymbolPath": - symbols.Add(this.ParseSymbolPathElement(child)); - break; - case "TypeLib": - this.ParseTypeLibElement(child, id.Id, null, win64); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, }; - var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context); - if (null != possibleKeyPath) - { - if (PossibleKeyPathType.None == possibleKeyPath.Type) - { - keyPathSet = YesNoType.No; - } - else - { - keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet; - - if (!String.IsNullOrEmpty(possibleKeyPath.Id)) - { - keyPossible = possibleKeyPath.Id; - } - - if (PossibleKeyPathType.Registry == possibleKeyPath.Type || PossibleKeyPathType.RegistryFormatted == possibleKeyPath.Type) - { - keyBit = ComponentKeyPathType.Registry; //MsiInterop.MsidbComponentAttributesRegistryKeyPath; - } - } - } - } - - // Verify that either the key path is not set, or it is set along with a key path ID. - Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible)); - - if (keyFound && YesNoType.Yes == keyPathSet) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - // if a possible KeyPath has been found and that value was explicitly set as - // the KeyPath of the component, set it now. Alternatively, if a possible - // KeyPath has been found and no KeyPath has been previously set, use this - // value as the default KeyPath of the component - if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound))) - { - keyFound = YesNoType.Yes == keyPathSet; - keyPath = keyPossible; - keyPathType = keyBit.Value; - } - } - - // Check for conditions that exclude this component from using implicit ids and/or generated guids. - var allowImplicitIds = true; - if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) - { - allowImplicitIds = false; - if (guid == "*") - { - this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); - } - } - else if (0 < files && ComponentKeyPathType.Registry == keyPathType) - { - allowImplicitIds = false; - if (guid == "*") - { - this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); - } - } - - // Check for implicit KeyPath which can easily be accidentally changed - if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) - { - this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); - } - - // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath. - // either an explicit KeyPath="yes" attribute must be specified or requirements for - // generatable guid must be met. - if (componentIdPlaceholder == id.Id) - { - if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) - { - this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); - - id = new Identifier(AccessModifier.Section, keyPath); - } - else - { - this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers)); - } - } - - // finally add the Component table row - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) - { - ComponentId = guid, - DirectoryRef = directoryId, - Location = location, - Condition = condition, - KeyPath = keyPath, - KeyPathType = keyPathType, - DisableRegistryReflection = disableRegistryReflection, - NeverOverwrite = neverOverwrite, - Permanent = permanent, - SharedDllRefCount = sharedDllRefCount, - Shared = shared, - Transitive = transitive, - UninstallWhenSuperseded = uninstallWhenSuperseded, - Win64 = win64, - }); - - if (multiInstance) - { - this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id) - { - ComponentRef = id.Id, - }); - } - - if (0 < symbols.Count) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id)) - { - SymbolType = SymbolPathType.Component, - SymbolId = id.Id, - SymbolPaths = String.Join(";", symbols), - }); - } - - // Complus - if (CompilerConstants.IntegerNotSet != comPlusBits) - { - this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers) - { - ComponentRef = id.Id, - ExpType = comPlusBits, - }); - } - - // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table - if (this.compilingModule) - { - this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false); - } - else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that. - { - // If the Component is defined directly under a feature, then mark the complex reference primary. - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType); - } - } - } - - /// - /// Parses a component group element. - /// - /// Element to parse. - /// - /// - private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directoryId = null; - string subdirectory = null; - string source = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - source = String.Concat(source, Path.DirectorySeparatorChar); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id)); - - // Add this componentGroup and its parent in WixGroup. - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id); - } - } - - /// - /// Parses a component group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element. - /// Identifier of parent element (usually a Feature or Module). - /// Optional language of parent (only useful for Modules). - private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) - { - Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary)); - } - - /// - /// Parses a component reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element. - /// Identifier of parent element (usually a Feature or Module). - /// Optional language of parent (only useful for Modules). - private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) - { - Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary)); - } - - /// - /// Parses a component search element. - /// - /// Element to parse. - /// Signature for search element. - private string ParseComponentSearchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string componentId = null; - var type = LocatorType.Filename; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Guid": - componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "directory": - type = LocatorType.Directory; - break; - case "file": - type = LocatorType.Filename; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("cmp", componentId, type.ToString()); - } - - var signature = id.Id; - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - - // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures - id = new Identifier(AccessModifier.Section, newId); - signature = null; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id) - { - SignatureRef = id.Id, - ComponentId = componentId, - Type = type, - }); - } - - return signature; - } - - /// - /// Parses a create folder element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Default identifier for directory to create. - /// true if the component is 64-bit. - /// Identifier for the directory that will be created - private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string subdirectory = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Shortcut": - this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); - break; - case "Permission": - this.ParsePermissionElement(child, directoryId, "CreateFolder"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, directoryId, "CreateFolder"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers) - { - DirectoryRef = directoryId, - ComponentRef = componentId, - }); - } - - return directoryId; - } - - /// - /// Parses a copy file element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of file to copy (null if moving the file). - private void ParseCopyFileElement(XElement node, string componentId, string fileId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var delete = false; - string destinationDirectory = null; - string destinationSubdirectory = null; - string destinationName = null; - string destinationShortName = null; - string destinationProperty = null; - string sourceDirectory = null; - string sourceSubdirectory = null; - string sourceFolder = null; - string sourceName = null; - string sourceProperty = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Delete": - delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DestinationDirectory": - destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory); - break; - case "DestinationSubdirectory": - destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "DestinationName": - destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib); - break; - case "DestinationProperty": - destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "DestinationShortName": - destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib); - break; - case "FileId": - if (null != fileId) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); - } - fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId); - break; - case "SourceDirectory": - sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory); - break; - case "SourceSubdirectory": - sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "SourceName": - sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SourceProperty": - sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory")); - } - - if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty")); - } - - if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory")); - } - - sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory"); - - if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory")); - } - - destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory"); - - if (null == id) - { - id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName); - } - - this.Core.ParseForExtensionElements(node); - - if (null == fileId) - { - // DestinationDirectory or DestinationProperty must be specified - if (null == destinationDirectory && null == destinationProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - SourceName = sourceName, - DestinationName = destinationName, - DestinationShortName = destinationShortName, - SourceFolder = sourceDirectory ?? sourceProperty, - DestFolder = destinationDirectory ?? destinationProperty, - Delete = delete, - }); - } - } - else // copy the file - { - if (null != sourceDirectory) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId")); - } - - if (null != sourceFolder) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId")); - } - - if (null != sourceName) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId")); - } - - if (null != sourceProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId")); - } - - if (delete) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId")); - } - - if (null == destinationName && null == destinationDirectory && null == destinationProperty) - { - this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FileRef = fileId, - DestinationName = destinationName, - DestinationShortName = destinationShortName, - DestinationFolder = destinationDirectory ?? destinationProperty, - }); - } - } - } - - /// - /// Parses a CustomAction element. - /// - /// Element to parse. - private void ParseCustomActionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var inlineScript = false; - var suppressModularization = YesNoType.NotSet; - string source = null; - string target = null; - var explicitWin64 = false; - - string scriptFile = null; - string subdirectory = null; - - CustomActionSourceType? sourceType = null; - CustomActionTargetType? targetType = null; - var executionType = CustomActionExecutionType.Immediate; - var hidden = false; - var impersonate = true; - var patchUninstall = false; - var tsAware = false; - var win64 = false; - var async = false; - var ignoreResult = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "BinaryRef": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.Binary; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - explicitWin64 = true; - win64 = false; - break; - case "always64": - explicitWin64 = true; - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "Directory": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.Directory; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source); - break; - case "DllEntry": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - targetType = CustomActionTargetType.Dll; - break; - case "Error": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.File; - targetType = CustomActionTargetType.TextData; - - // The target can be either a formatted error string or a literal - // error number. Try to convert to error number to determine whether - // to add a reference. No need to look at the value. - if (Int32.TryParse(target, out var ignored)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target); - } - break; - case "ExeCommand": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.Exe; - break; - case "Execute": - var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (execute) - { - case "commit": - executionType = CustomActionExecutionType.Commit; - break; - case "deferred": - executionType = CustomActionExecutionType.Deferred; - break; - case "firstSequence": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "immediate": - executionType = CustomActionExecutionType.Immediate; - break; - case "oncePerProcess": - executionType = CustomActionExecutionType.OncePerProcess; - break; - case "rollback": - executionType = CustomActionExecutionType.Rollback; - break; - case "secondSequence": - executionType = CustomActionExecutionType.ClientRepeat; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence")); - break; - } - break; - case "FileRef": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.File; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File - break; - case "HideTarget": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Impersonate": - impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "JScriptCall": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.JScript; - break; - case "PatchUninstall": - patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Property": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.Property; - break; - case "Return": - var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (returnValue) - { - case "asyncNoWait": - async = true; - ignoreResult = true; - break; - case "asyncWait": - async = true; - break; - case "check": - break; - case "ignore": - ignoreResult = true; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore")); - break; - } - break; - case "Script": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - - // set the source and target to empty string for error messages when the user sets multiple sources or targets - source = String.Empty; - target = String.Empty; - - inlineScript = true; - - var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (script) - { - case "jscript": - sourceType = CustomActionSourceType.Directory; - targetType = CustomActionTargetType.JScript; - break; - case "vbscript": - sourceType = CustomActionSourceType.Directory; - targetType = CustomActionTargetType.VBScript; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript")); - break; - } - break; - case "ScriptSourceFile": - scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TerminalServerAware": - tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.TextData; - break; - case "VBScriptCall": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.VBScript; - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) - { - win64 = true; - } - - if (!String.IsNullOrEmpty(subdirectory)) - { - if (sourceType == CustomActionSourceType.Directory) - { - source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory"); - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory")); - } - } - - // if we have an in-lined Script CustomAction ensure no source or target attributes were provided - if (inlineScript) - { - if (String.IsNullOrEmpty(scriptFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); - } - } - else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript - { - if (null == source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property")); - } - else if (CustomActionSourceType.Directory == sourceType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory")); - } - } - else if (CustomActionTargetType.JScript == targetType) // non-inline jscript - { - if (null == source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property")); - } - else if (CustomActionSourceType.Directory == sourceType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory")); - } - } - else if (CustomActionTargetType.Exe == targetType) // exe-command - { - if (null == source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property")); - } - } - else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); - } - - if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); - } - - if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall")); - } - - if (async && ignoreResult && CustomActionTargetType.Exe != targetType) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand")); - } - - // TS-aware CAs are valid only when deferred. - if (tsAware & - CustomActionExecutionType.Deferred != executionType && - CustomActionExecutionType.Rollback != executionType && - CustomActionExecutionType.Commit != executionType) - { - this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers)); - } - - // MSI doesn't support in-script property setting, so disallow it - if (CustomActionSourceType.Property == sourceType && - CustomActionTargetType.TextData == targetType && - (CustomActionExecutionType.Deferred == executionType || - CustomActionExecutionType.Rollback == executionType || - CustomActionExecutionType.Commit == executionType)) - { - this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers)); - } - - if (!targetType.HasValue /*0 == targetBits*/) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id) - { - ExecutionType = executionType, - Source = source, - SourceType = sourceType.Value, - Target = target, - TargetType = targetType.Value, - Async = async, - IgnoreResult = ignoreResult, - Impersonate = impersonate, - PatchUninstall = patchUninstall, - TSAware = tsAware, - Win64 = win64, - Hidden = hidden, - ScriptFile = new IntermediateFieldPathValue { Path = scriptFile } - }); - - if (YesNoType.Yes == suppressModularization) - { - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) - { - SuppressIdentifier = id.Id - }); - } - } - } - - /// - /// Parses a simple reference element. - /// - /// Element to parse. - /// Symbol which contains the target of the simple reference. - /// Id of the referenced element. - private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; - } - - /// - /// Parses a PatchFamilyRef element. - /// - /// Element to parse. - /// The parent type. - /// The ID of the parent. - /// Id of the referenced element. - private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var primaryKeys = new string[2]; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ProductCode": - primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == primaryKeys[0]) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchSequence, primaryKeys); - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true); - } - } - - /// - /// Parses an ensure table element. - /// - /// Element to parse. - private void ParseEnsureTableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (31 < id.Length) - { - this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id)); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.EnsureTable(sourceLineNumbers, id); - } - - /// - /// Parses a custom table element. - /// - /// Element to parse. - /// not cleaned - private void ParseCustomTableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string tableId = null; - var unreal = false; - var columns = new List(); - var foundColumns = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Unreal": - unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == tableId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (31 < tableId.Length) - { - this.Core.Write(ErrorMessages.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Column": - foundColumns = true; - - var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId); - if (column != null) - { - columns.Add(column); - } - break; - case "Row": - this.ParseRowElement(child, childSourceLineNumbers, tableId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (columns.Count > 0) - { - if (!columns.Where(c => c.PrimaryKey).Any()) - { - this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); - } - - if (!this.Core.EncounteredError) - { - var columnNames = String.Join(new string(WixCustomTableSymbol.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); - - this.Core.AddSymbol(new WixCustomTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, tableId)) - { - ColumnNames = columnNames, - Unreal = unreal, - }); - } - else if (!foundColumns) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Column")); - } - } - } - - /// - /// Parses a CustomTableRef element. - /// - /// Element to parse. - private void ParseCustomTableRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string tableId = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == tableId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Row": - this.ParseRowElement(child, childSourceLineNumbers, tableId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a Column element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// Table Id. - private WixCustomTableColumnSymbol ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId) - { - string columnName = null; - IntermediateFieldType? columnType = null; - var description = String.Empty; - int? keyColumn = null; - var keyTable = String.Empty; - var localizable = false; - long? maxValue = null; - long? minValue = null; - WixCustomTableColumnCategoryType? category = null; - var modularization = WixCustomTableColumnModularizeType.None; - var nullable = false; - var primaryKey = false; - var setValues = String.Empty; - var columnUnreal = false; - var width = 0; - - foreach (var childAttrib in child.Attributes()) - { - switch (childAttrib.Name.LocalName) - { - case "Id": - columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); - break; - case "Category": - var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (categoryValue) - { - case "text": - category = WixCustomTableColumnCategoryType.Text; - break; - case "upperCase": - category = WixCustomTableColumnCategoryType.UpperCase; - break; - case "lowerCase": - category = WixCustomTableColumnCategoryType.LowerCase; - break; - case "integer": - category = WixCustomTableColumnCategoryType.Integer; - break; - case "doubleInteger": - category = WixCustomTableColumnCategoryType.DoubleInteger; - break; - case "timeDate": - category = WixCustomTableColumnCategoryType.TimeDate; - break; - case "identifier": - category = WixCustomTableColumnCategoryType.Identifier; - break; - case "property": - category = WixCustomTableColumnCategoryType.Property; - break; - case "filename": - category = WixCustomTableColumnCategoryType.Filename; - break; - case "wildCardFilename": - category = WixCustomTableColumnCategoryType.WildCardFilename; - break; - case "path": - category = WixCustomTableColumnCategoryType.Path; - break; - case "paths": - category = WixCustomTableColumnCategoryType.Paths; - break; - case "anyPath": - category = WixCustomTableColumnCategoryType.AnyPath; - break; - case "defaultDir": - category = WixCustomTableColumnCategoryType.DefaultDir; - break; - case "regPath": - category = WixCustomTableColumnCategoryType.RegPath; - break; - case "formatted": - category = WixCustomTableColumnCategoryType.Formatted; - break; - case "formattedSddl": - category = WixCustomTableColumnCategoryType.FormattedSddl; - break; - case "template": - category = WixCustomTableColumnCategoryType.Template; - break; - case "condition": - category = WixCustomTableColumnCategoryType.Condition; - break; - case "guid": - category = WixCustomTableColumnCategoryType.Guid; - break; - case "version": - category = WixCustomTableColumnCategoryType.Version; - break; - case "language": - category = WixCustomTableColumnCategoryType.Language; - break; - case "binary": - category = WixCustomTableColumnCategoryType.Binary; - break; - case "customSource": - category = WixCustomTableColumnCategoryType.CustomSource; - break; - case "cabinet": - category = WixCustomTableColumnCategoryType.Cabinet; - break; - case "shortcut": - category = WixCustomTableColumnCategoryType.Shortcut; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, - "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", - "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", - "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } - break; - case "Description": - description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - case "KeyColumn": - keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); - break; - case "KeyTable": - keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - case "Localizable": - localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - case "MaxValue": - maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); - break; - case "MinValue": - minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); - break; - case "Modularize": - var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (modularizeValue) - { - case "column": - modularization = WixCustomTableColumnModularizeType.Column; - break; - case "companionFile": - modularization = WixCustomTableColumnModularizeType.CompanionFile; - break; - case "condition": - modularization = WixCustomTableColumnModularizeType.Condition; - break; - case "controlEventArgument": - modularization = WixCustomTableColumnModularizeType.ControlEventArgument; - break; - case "controlText": - modularization = WixCustomTableColumnModularizeType.ControlText; - break; - case "icon": - modularization = WixCustomTableColumnModularizeType.Icon; - break; - case "none": - modularization = WixCustomTableColumnModularizeType.None; - break; - case "property": - modularization = WixCustomTableColumnModularizeType.Property; - break; - case "semicolonDelimited": - modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } - break; - case "Nullable": - nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - case "PrimaryKey": - primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - case "Set": - setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (typeValue) - { - case "binary": - columnType = IntermediateFieldType.Path; - break; - case "int": - columnType = IntermediateFieldType.Number; - break; - case "string": - columnType = IntermediateFieldType.String; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } - break; - case "Width": - width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); - break; - case "Unreal": - columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - default: - this.Core.UnexpectedAttribute(child, childAttrib); - break; - } - } - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); - } - - if (!columnType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); - } - else if (columnType == IntermediateFieldType.Number) - { - if (2 != width && 4 != width) - { - this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); - } - } - else if (columnType == IntermediateFieldType.Path) - { - if (!category.HasValue) - { - category = WixCustomTableColumnCategoryType.Binary; - } - else if (category != WixCustomTableColumnCategoryType.Binary) - { - this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); - } - } - - this.Core.ParseForExtensionElements(child); - - if (this.Core.EncounteredError) - { - return null; - } - - var attributes = primaryKey ? WixCustomTableColumnSymbolAttributes.PrimaryKey : WixCustomTableColumnSymbolAttributes.None; - attributes |= localizable ? WixCustomTableColumnSymbolAttributes.Localizable : WixCustomTableColumnSymbolAttributes.None; - attributes |= nullable ? WixCustomTableColumnSymbolAttributes.Nullable : WixCustomTableColumnSymbolAttributes.None; - attributes |= columnUnreal ? WixCustomTableColumnSymbolAttributes.Unreal : WixCustomTableColumnSymbolAttributes.None; - - var column = this.Core.AddSymbol(new WixCustomTableColumnSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, columnName)) - { - TableRef = tableId, - Name = columnName, - Type = columnType.Value, - Attributes = attributes, - Width = width, - Category = category, - Description = description, - KeyColumn = keyColumn, - KeyTable = keyTable, - MaxValue = maxValue, - MinValue = minValue, - Modularize = modularization, - Set = setValues, - }); - return column; - } - - /// - /// Parses a Row element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// Table Id. - private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId) - { - var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); - - foreach (var attrib in node.Attributes()) - { - this.Core.ParseExtensionAttribute(node, attrib); - } - - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Data": - string columnName = null; - string data = null; - foreach (var attrib in child.Attributes()) - { - switch (attrib.Name.LocalName) - { - case "Column": - columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Value": - data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.ParseExtensionAttribute(child, attrib); - break; - } - } - - this.Core.InnerTextDisallowed(node); - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixCustomTableCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, rowId, columnName)) - { - RowId = rowId, - ColumnRef = columnName, - TableRef = tableId, - Data = data - }); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - - if (!this.Core.EncounteredError) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); - } - } - - /// - /// Parses a directory element. - /// - /// Element to parse. - /// Optional identifier of parent directory. - /// Disk id inherited from parent directory. - /// Path to source file as of yet. - private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string componentGuidGenerationSeed = null; - var fileSourceAttribSet = false; - XAttribute nameAttribute = null; - var name = "."; // default to parent directory. - string shortName = null; - string sourceName = null; - string shortSourceName = null; - string symbols = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ComponentGuidGenerationSeed": - componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "FileSource": - fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - fileSourceAttribSet = true; - break; - case "Name": - if ("." == attrib.Value) - { - name = attrib.Value; - } - else - { - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - } - nameAttribute = attrib; - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "ShortSourceName": - shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "SourceName": - if ("." == attrib.Value) - { - sourceName = attrib.Value; - } - else - { - sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (nameAttribute == null) - { - if (!String.IsNullOrEmpty(shortName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); - } - } - else if (!String.IsNullOrEmpty(name)) - { - if (String.IsNullOrEmpty(shortName)) - { - } - else if (name == ".") - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name)); - } - else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name)); - } - } - - if (String.IsNullOrEmpty(sourceName)) - { - if (!String.IsNullOrEmpty(shortSourceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName")); - } - } - else - { - if (String.IsNullOrEmpty(shortSourceName)) - { - } - else if (sourceName == ".") - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName)); - } - else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName)); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); - } - else if (WindowsInstallerStandard.IsStandardDirectory(id.Id)) - { - if (String.IsNullOrEmpty(sourceName)) - { - this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id)); - } - - if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null) - { - this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); - } - } - - // Update the file source path appropriately. - if (fileSourceAttribSet) - { - if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); - } - } - else // add the appropriate part of this directory element to the file source. - { - string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; - - if (!String.IsNullOrEmpty(append)) - { - fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource); - break; - case "Directory": - this.ParseDirectoryElement(child, id.Id, diskId, fileSource); - break; - case "Merge": - this.ParseMergeElement(child, id.Id, diskId); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) - { - ParentDirectoryRef = parentId, - Name = name, - ShortName = shortName, - SourceName = sourceName, - SourceShortName = shortSourceName, - ComponentGuidGenerationSeed = componentGuidGenerationSeed - }); - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id) - { - SymbolType = SymbolPathType.Directory, - SymbolId = id.Id, - SymbolPaths = symbols, - }); - } - } - } - - /// - /// Parses a directory reference element. - /// - /// Element to parse. - private void ParseDirectoryRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var diskId = CompilerConstants.IntegerNotSet; - var fileSource = String.Empty; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "FileSource": - fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (WindowsInstallerStandard.IsStandardDirectory(id)) - { - this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id)); - } - - if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource); - break; - case "Directory": - this.ParseDirectoryElement(child, id, diskId, fileSource); - break; - case "Merge": - this.ParseMergeElement(child, id, diskId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a directory search element. - /// - /// Element to parse. - /// Signature of parent search element. - /// Signature of search element. - private string ParseDirectorySearchElement(XElement node, string parentSignature) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var depth = CompilerConstants.IntegerNotSet; - string path = null; - var assignToProperty = false; - string signature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Depth": - depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Path": - path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "AssignToProperty": - assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("dir", path, depth.ToString()); - } - - signature = id.Id; - - var oneChild = false; - var hasFileSearch = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - hasFileSearch = true; - signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth); - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - - // If AssignToProperty is set, only a FileSearch - // or no child element can be nested. - if (assignToProperty) - { - if (!hasFileSearch) - { - this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName)); - } - else if (!oneChild) - { - // This a normal directory search. - assignToProperty = false; - } - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var access = id.Access; - var rowId = id.Id; - - // If AssignToProperty is set, the DrLocator row created by - // ParseFileSearchElement creates the directory entry to return - // and the row created here is for the file search. - if (assignToProperty) - { - access = AccessModifier.Section; - rowId = signature; - - // The property should be set to the directory search Id. - signature = id.Id; - } - - var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path)) - { - SignatureRef = rowId, - Parent = parentSignature, - Path = path, - }); - - if (CompilerConstants.IntegerNotSet != depth) - { - symbol.Depth = depth; - } - } - - return signature; - } - - /// - /// Parses a directory search reference element. - /// - /// Element to parse. - /// Signature of parent search element. - /// Signature of search element. - private string ParseDirectorySearchRefElement(XElement node, string parentSignature) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - Identifier parent = null; - string path = null; - string signature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Parent": - parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Path": - path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != parent) - { - if (!String.IsNullOrEmpty(parentSignature)) - { - this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature)); - } - else - { - parentSignature = parent.Id; - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("dsr", parentSignature, path); - } - - signature = id.Id; - - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path); - - return signature; - } - - /// - /// Parses a feature element. - /// - /// Element to parse. - /// The type of parent. - /// Optional identifer for parent feature. - /// Display value for last feature used to get the features to display in the same order as specified - /// in the source code. - private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string configurableDirectory = null; - string description = null; - var displayValue = "collapse"; - var level = 1; - string title = null; - - var installDefault = FeatureInstallDefault.Local; - var typicalDefault = FeatureTypicalDefault.Install; - var disallowAbsent = false; - var disallowAdvertise = false; - var display = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "AllowAbsent": - disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); - break; - case "AllowAdvertise": - disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); - break; - case "ConfigurableDirectory": - configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Display": - displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "InstallDefault": - var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installDefaultValue) - { - case "followParent": - if (ComplexReferenceParentType.Product == parentType) - { - this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers)); - } - //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent; - installDefault = FeatureInstallDefault.FollowParent; - break; - case "local": // this is the default - installDefault = FeatureInstallDefault.Local; - break; - case "source": - //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource; - installDefault = FeatureInstallDefault.Source; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source")); - break; - } - break; - case "Level": - level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Title": - title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-FEATURE-TITLE-HERE" == title) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title)); - } - break; - case "TypicalDefault": - var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typicalValue) - { - case "advertise": - //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise; - typicalDefault = FeatureTypicalDefault.Advertise; - break; - case "install": // this is the default - typicalDefault = FeatureTypicalDefault.Install; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (38 < id.Id.Length) - { - this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - - if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory) - { - this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory)); - } - - if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise) - { - this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no")); - } - - var childDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id); - break; - case "Level": - this.ParseLevelElement(child, id.Id); - break; - case "MergeRef": - this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - switch (displayValue) - { - case "collapse": - lastDisplay = (lastDisplay | 1) + 1; - display = lastDisplay; - break; - case "expand": - lastDisplay = (lastDisplay + 1) | 1; - display = lastDisplay; - break; - case "hidden": - display = 0; - break; - default: - if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden")); - } - else - { - // Save the display value (if its not hidden) for subsequent rows - if (0 != display) - { - lastDisplay = display; - } - } - break; - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id) - { - ParentFeatureRef = null, // this field is set in the linker - Title = title, - Description = description, - Display = display, - Level = level, - DirectoryRef = configurableDirectory, - DisallowAbsent = disallowAbsent, - DisallowAdvertise = disallowAdvertise, - InstallDefault = installDefault, - TypicalDefault = typicalDefault, - }); - - if (ComplexReferenceParentType.Unknown != parentType) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false); - } - } - } - - /// - /// Parses a feature reference element. - /// - /// Element to parse. - /// The type of parent. - /// Optional identifier for parent feature. - private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var ignoreParent = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id); - break; - case "IgnoreParent": - ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - var lastDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay); - break; - case "FeatureGroup": - this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); - break; - case "MergeRef": - this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false); - } - } - } - - /// - /// Parses a feature group element. - /// - /// Element to parse. - /// - /// - private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - var lastDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); - break; - case "MergeRef": - this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id)); - - //Add this FeatureGroup and its parent in WixGroup. - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id); - } - } - - /// - /// Parses a feature group reference element. - /// - /// Element to parse. - /// The type of parent. - /// Identifier of parent element. - private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var ignoreParent = YesNoType.NotSet; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id); - break; - case "IgnoreParent": - ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (YesNoType.Yes != ignoreParent) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary)); - } - } - } - - /// - /// Parses an environment element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseEnvironmentElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - EnvironmentActionType? action = null; - EnvironmentPartType? part = null; - var permanent = false; - var separator = ";"; // default to ';' - var system = false; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "create": - action = EnvironmentActionType.Create; - break; - case "set": - action = EnvironmentActionType.Set; - break; - case "remove": - action = EnvironmentActionType.Remove; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove")); - break; - } - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Part": - var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (partValue) - { - case "all": - part = EnvironmentPartType.All; - break; - case "first": - part = EnvironmentPartType.First; - break; - case "last": - part = EnvironmentPartType.Last; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last")); - break; - } - break; - case "Permanent": - permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Separator": - separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "System": - system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString()); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (part.HasValue && action == EnvironmentActionType.Create) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); - } - - //if (Wix.Environment.PartType.NotSet != partType) - //{ - // if ("+" == action) - // { - // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); - // } - - // switch (partType) - // { - // case Wix.Environment.PartType.all: - // break; - // case Wix.Environment.PartType.first: - // text = String.Concat(text, separator, "[~]"); - // break; - // case Wix.Environment.PartType.last: - // text = String.Concat("[~]", separator, text); - // break; - // } - //} - - //if (permanent) - //{ - // uninstall = null; - //} - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id) - { - Name = name, - Value = value, - Separator = separator, - Action = action, - Part = part, - Permanent = permanent, - System = system, - ComponentRef = componentId - }); - } - } - - /// - /// Parses an error element. - /// - /// Element to parse. - private void ParseErrorElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var id = CompilerConstants.IntegerNotSet; - string message = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Message": - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = CompilerConstants.IllegalInteger; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) - { - Message = message - }); - } - } - - /// - /// Parses an extension element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Flag if this extension is advertised. - /// ProgId for extension. - private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string extension = null; - string mime = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Advertise": - var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString())); - } - advertise = extensionAdvertise; - break; - case "ContentType": - mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - var context = new Dictionary() { { "ProgId", progId }, { "ComponentId", componentId } }; - this.Core.ParseExtensionAttribute(node, attrib, context); - } - } - - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Verb": - this.ParseVerbElement(child, extension, progId, componentId, advertise); - break; - case "MIME": - var newMime = this.ParseMIMEElement(child, extension, componentId, advertise); - if (null != newMime && null == mime) - { - mime = newMime; - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (YesNoType.Yes == advertise) - { - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ExtensionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, extension, componentId)) - { - Extension = extension, - ComponentRef = componentId, - ProgIdRef = progId, - MimeRef = mime, - FeatureRef = Guid.Empty.ToString("B"), - }); - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Verb); - } - } - else if (YesNoType.No == advertise) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension - if (null != mime) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType - } - } - } - - - /// - /// Parses a file element. - /// - /// File element to parse. - /// Parent's component id. - /// Ancestor's directory id. - /// Disk id inherited from parent component. - /// Default source path of parent directory. - /// This will be set with the possible keyPath for the parent component. - /// true if the component is 64-bit. - /// - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var assemblyType = AssemblyType.NotAnAssembly; - string assemblyApplication = null; - string assemblyManifest = null; - string bindPath = null; - - //int bits = MsiInterop.MsidbFileAttributesVital; - var readOnly = false; - var checksum = false; - bool? compressed = null; - var hidden = false; - var system = false; - var vital = true; // assume all files are vital. - - string companionFile = null; - string defaultLanguage = null; - var defaultSize = 0; - string defaultVersion = null; - string fontTitle = null; - var keyPath = YesNoType.NotSet; - string name = null; - var patchGroup = CompilerConstants.IntegerNotSet; - var patchIgnore = false; - var patchIncludeWholeFile = false; - var patchAllowIgnoreOnError = false; - - string ignoreLengths = null; - string ignoreOffsets = null; - string protectLengths = null; - string protectOffsets = null; - string symbols = null; - - string procArch = null; - int? selfRegCost = null; - string shortName = null; - var source = sourcePath; // assume we'll use the parents as the source for this file - var sourceSet = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Assembly": - var assemblyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (assemblyValue) - { - case ".net": - assemblyType = AssemblyType.DotNetAssembly; - break; - case "no": - assemblyType = AssemblyType.NotAnAssembly; - break; - case "win32": - assemblyType = AssemblyType.Win32Assembly; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net")); - break; - } - break; - case "AssemblyApplication": - assemblyApplication = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyApplication); - break; - case "AssemblyManifest": - assemblyManifest = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyManifest); - break; - case "BindPath": - bindPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Checksum": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - checksum = true; - //bits |= MsiInterop.MsidbFileAttributesChecksum; - } - break; - case "CompanionFile": - companionFile = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, companionFile); - break; - case "Compressed": - var compressedValue = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - if (YesNoDefaultType.Yes == compressedValue) - { - compressed = true; - //bits |= MsiInterop.MsidbFileAttributesCompressed; - } - else if (YesNoDefaultType.No == compressedValue) - { - compressed = false; - //bits |= MsiInterop.MsidbFileAttributesNoncompressed; - } - break; - case "DefaultLanguage": - defaultLanguage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DefaultSize": - defaultSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "DefaultVersion": - defaultVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "FontTitle": - fontTitle = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - hidden = true; - //bits |= MsiInterop.MsidbFileAttributesHidden; - } - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "PatchGroup": - patchGroup = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); - break; - case "PatchIgnore": - patchIgnore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "PatchWholeFile": - patchIncludeWholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "PatchAllowIgnoreOnError": - patchAllowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ProcessorArchitecture": - var procArchValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (procArchValue) - { - case "msil": - procArch = "MSIL"; - break; - case "x86": - procArch = "x86"; - break; - case "x64": - procArch = "amd64"; - break; - case "arm64": - procArch = "arm64"; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64")); - break; - } - break; - case "ReadOnly": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - readOnly = true; - //bits |= MsiInterop.MsidbFileAttributesReadOnly; - } - break; - case "SelfRegCost": - selfRegCost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - sourceSet = true; - break; - case "System": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - system = true; - //bits |= MsiInterop.MsidbFileAttributesSystem; - } - break; - case "TrueType": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - fontTitle = String.Empty; - } - break; - case "Vital": - var isVital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (YesNoType.Yes == isVital) - { - vital = true; - //bits |= MsiInterop.MsidbFileAttributesVital; - } - else if (YesNoType.No == isVital) - { - vital = false; - //bits &= ~MsiInterop.MsidbFileAttributesVital; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != companionFile) - { - // the companion file cannot be the key path of a component - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes")); - } - } - - if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name) - { - name = Path.GetFileName(source); - if (!this.Core.IsValidLongFilename(name, false)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); - } - } - - if (name == null) - { - if (shortName == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else - { - name = shortName; - shortName = null; - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("fil", directoryId, name); - } - - if (null != defaultVersion && null != companionFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); - } - - if (AssemblyType.NotAnAssembly == assemblyType) - { - if (null != assemblyManifest) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest")); - } - - if (null != assemblyApplication) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication")); - } - } - else - { - if (AssemblyType.Win32Assembly == assemblyType && null == assemblyManifest) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32")); - } - - // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath - if (YesNoType.Yes != keyPath && "*" != componentGuid) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (AssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes")); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); - break; - case "AssemblyName": - this.ParseAssemblyName(child, componentId); - break; - case "Class": - this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); - break; - case "CopyFile": - this.ParseCopyFileElement(child, componentId, id.Id); - break; - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ODBCDriver": - this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); - break; - case "ODBCTranslator": - this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); - break; - case "Permission": - this.ParsePermissionElement(child, id.Id, "File"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "File"); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "Shortcut": - this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "TypeLib": - this.ParseTypeLibElement(child, componentId, id.Id, win64Component); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError) - { - var patchAttributes = PatchAttributeType.None; - if (patchIgnore) - { - patchAttributes |= PatchAttributeType.Ignore; - } - if (patchIncludeWholeFile) - { - patchAttributes |= PatchAttributeType.IncludeWholeFile; - } - if (patchAllowIgnoreOnError) - { - patchAttributes |= PatchAttributeType.AllowIgnoreOnError; - } - - if (String.IsNullOrEmpty(source)) - { - source = name; - } - else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name - { - source = Path.Combine(source, name); - } - - var attributes = FileSymbolAttributes.None; - attributes |= readOnly ? FileSymbolAttributes.ReadOnly : 0; - attributes |= hidden ? FileSymbolAttributes.Hidden : 0; - attributes |= system ? FileSymbolAttributes.System : 0; - attributes |= vital ? FileSymbolAttributes.Vital : 0; - attributes |= checksum ? FileSymbolAttributes.Checksum : 0; - attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0; - attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0; - - this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Name = name, - ShortName = shortName, - FileSize = defaultSize, - Version = companionFile ?? defaultVersion, - Language = defaultLanguage, - Attributes = attributes, - - DirectoryRef = directoryId, - DiskId = (CompilerConstants.IntegerNotSet == diskId) ? null : (int?)diskId, - Source = new IntermediateFieldPathValue { Path = source }, - - FontTitle = fontTitle, - SelfRegCost = selfRegCost, - BindPath = bindPath, - - PatchGroup = (CompilerConstants.IntegerNotSet == patchGroup) ? null : (int?)patchGroup, - PatchAttributes = patchAttributes, - - // Delta patching information - RetainLengths = protectLengths, - IgnoreOffsets = ignoreOffsets, - IgnoreLengths = ignoreLengths, - RetainOffsets = protectOffsets, - SymbolPaths = symbols, - }); - - if (AssemblyType.NotAnAssembly != assemblyType) - { - this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FeatureRef = Guid.Empty.ToString("B"), - ManifestFileRef = assemblyManifest, - ApplicationFileRef = assemblyApplication, - Type = assemblyType, - ProcessorArchitecture = procArch, - }); - } - } - - if (CompilerConstants.IntegerNotSet != diskId) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - - // If this component does not have a companion file this file is a possible keypath. - possibleKeyPath = null; - if (null == companionFile) - { - possibleKeyPath = id.Id; - } - - return keyPath; - } - - /// - /// Parses a file search element. - /// - /// Element to parse. - /// Signature of parent search element. - /// Whether this search element is used to search for the parent directory. - /// The depth specified by the parent search element. - /// Signature of search element. - private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string languages = null; - var minDate = CompilerConstants.IntegerNotSet; - var maxDate = CompilerConstants.IntegerNotSet; - var maxSize = CompilerConstants.IntegerNotSet; - var minSize = CompilerConstants.IntegerNotSet; - string maxVersion = null; - string minVersion = null; - string name = null; - string shortName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "MinVersion": - minVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MaxVersion": - maxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MinSize": - minSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "MaxSize": - maxSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "MinDate": - minDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); - break; - case "MaxDate": - maxDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); - break; - case "Languages": - languages = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Using both ShortName and Name will not always work due to a Windows Installer bug. - if (null != shortName && null != name) - { - this.Core.Write(WarningMessages.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); - } - else if (null == shortName && null == name) // at least one name must be specified. - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (this.Core.IsValidShortFilename(name, false)) - { - if (null == shortName) - { - shortName = name; - name = null; - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); - } - } - - if (null == id) - { - if (String.IsNullOrEmpty(parentSignature)) - { - id = this.Core.CreateIdentifier("fs", name ?? shortName); - } - else // reuse parent signature in the Signature table - { - id = new Identifier(AccessModifier.Section, parentSignature); - } - } - - var isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal); - if (parentDirectorySearch) - { - // If searching for the parent directory, the Id attribute - // value must be specified and unique. - if (isSameId) - { - this.Core.Write(ErrorMessages.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName)); - } - } - else if (parentDepth > 1) - { - // Otherwise, if the depth > 1 the Id must be absent or the same - // as the parent DirectorySearch if AssignToProperty is not set. - if (!isSameId) - { - this.Core.Write(ErrorMessages.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature)); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new SignatureSymbol(sourceLineNumbers, id) - { - FileName = name ?? shortName, - MinVersion = minVersion, - MaxVersion = maxVersion, - Languages = languages - }); - - if (CompilerConstants.IntegerNotSet != minSize) - { - symbol.MinSize = minSize; - } - - if (CompilerConstants.IntegerNotSet != maxSize) - { - symbol.MaxSize = maxSize; - } - - if (CompilerConstants.IntegerNotSet != minDate) - { - symbol.MinDate = minDate; - } - - if (CompilerConstants.IntegerNotSet != maxDate) - { - symbol.MaxDate = maxDate; - } - - // Create a DrLocator row to associate the file with a directory - // when a different identifier is specified for the FileSearch. - if (!isSameId) - { - if (parentDirectorySearch) - { - // Creates the DrLocator row for the directory search while - // the parent DirectorySearch creates the file locator row. - this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, parentSignature, id.Id, String.Empty)) - { - SignatureRef = parentSignature, - Parent = id.Id - }); - } - else - { - this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id.Id, parentSignature, String.Empty)) - { - SignatureRef = id.Id, - Parent = parentSignature - }); - } - } - } - - return id.Id; // the id of the FileSearch element is its signature - } - - - /// - /// Parses a fragment element. - /// - /// Element to parse. - private void ParseFragmentElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. - - this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId); - - var featureDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "_locDefinition": - break; - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "BootstrapperApplication": - this.ParseBootstrapperApplicationElement(child); - break; - case "BootstrapperApplicationRef": - this.ParseBootstrapperApplicationRefElement(child); - break; - case "BundleCustomData": - this.ParseBundleCustomDataElement(child); - break; - case "BundleCustomDataRef": - this.ParseBundleCustomDataRefElement(child); - break; - case "BundleExtension": - this.ParseBundleExtensionElement(child); - break; - case "BundleExtensionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); - break; - case "ComplianceCheck": - this.ParseComplianceCheckElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroup": - this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); - break; - case "Container": - this.ParseContainerElement(child); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay); - break; - case "FeatureGroup": - this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "Media": - this.ParseMediaElement(child, null); - break; - case "MediaTemplate": - this.ParseMediaTemplateElement(child, null); - break; - case "Launch": - this.ParseLaunchElement(child); - break; - case "PackageGroup": - this.ParsePackageGroupElement(child); - break; - case "PackageCertificates": - case "PatchCertificates": - this.ParseCertificatesElement(child); - break; - case "PatchFamily": - this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id); - break; - case "PatchFamilyGroup": - this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id); - break; - case "PatchFamilyGroupRef": - this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id); - break; - case "PayloadGroup": - this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "RelatedBundle": - this.ParseRelatedBundleElement(child); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SetVariable": - this.ParseSetVariableElement(child); - break; - case "SetVariableRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "StandardDirectory": - this.ParseStandardDirectoryElement(child); - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "Upgrade": - this.ParseUpgradeElement(child); - break; - case "Variable": - this.ParseVariableElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError && null != id) - { - this.Core.AddSymbol(new WixFragmentSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a launch condition element. - /// - /// Element to parse. - private void ParseLaunchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - string message = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Message": - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(condition)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); - } - - if (String.IsNullOrEmpty(message)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); - } - - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) - { - Condition = condition, - Description = message - }); - } - } - - /// - /// Parses a IniFile element. - /// - /// Element to parse. - /// Identifier of the parent component. - private void ParseIniFileElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - IniFileActionType? action = null; - string directory = null; - string key = null; - string name = null; - string section = null; - string shortName = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "addLine": - action = IniFileActionType.AddLine; - break; - case "addTag": - action = IniFileActionType.AddTag; - break; - case "removeLine": - action = IniFileActionType.RemoveLine; - break; - case "removeTag": - action = IniFileActionType.RemoveTag; - break; - case "": // error case handled by GetAttributeValue() - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag")); - break; - } - break; - case "Directory": - directory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Section": - section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!action.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - else if (IniFileActionType.AddLine == action || IniFileActionType.AddTag == action || IniFileActionType.CreateLine == action) - { - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == section) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new IniFileSymbol(sourceLineNumbers, id) - { - FileName = name, - ShortFileName = shortName, - DirProperty = directory, - Section = section, - Key = key, - Value = value, - Action = action.Value, - ComponentRef = componentId - }); - } - } - - /// - /// Parses an IniFile search element. - /// - /// Element to parse. - /// Signature for search element. - private string ParseIniFileSearchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var field = CompilerConstants.IntegerNotSet; - string key = null; - string name = null; - string section = null; - string shortName = null; - string signature = null; - var type = 1; // default is file - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Field": - field = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Section": - section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "directory": - type = 0; - break; - case "file": - type = 1; - break; - case "raw": - type = 2; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == section) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString()); - } - - signature = id.Id; - - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - - // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures - id = new Identifier(AccessModifier.Section, newId); - signature = null; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new IniLocatorSymbol(sourceLineNumbers, id) - { - FileName = name, - ShortFileName = shortName, - Section = section, - Key = key, - Type = type - }); - - if (CompilerConstants.IntegerNotSet != field) - { - symbol.Field = field; - } - } - - return signature; - } - - /// - /// Parses an isolated component element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseIsolateComponentElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string shared = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Shared": - shared = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, shared); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == shared) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new IsolatedComponentSymbol(sourceLineNumbers) - { - SharedComponentRef = shared, - ApplicationComponentRef = componentId - }); - } - } - - /// - /// Parses a PatchCertificates or PackageCertificates element. - /// - /// The element to parse. - private void ParseCertificatesElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - // no attributes are supported for this element - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - this.Core.UnexpectedAttribute(node, attrib); - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DigitalCertificate": - var name = this.ParseDigitalCertificateElement(child); - - if (!this.Core.EncounteredError) - { - if ("PatchCertificates" == node.Name.LocalName) - { - this.Core.AddSymbol(new MsiPatchCertificateSymbol(sourceLineNumbers) - { - PatchCertificate = name, - DigitalCertificateRef = name, - }); - } - else - { - this.Core.AddSymbol(new MsiPackageCertificateSymbol(sourceLineNumbers) - { - PackageCertificate = name, - DigitalCertificateRef = name, - }); - } - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses an digital certificate element. - /// - /// Element to parse. - /// The identifier of the certificate. - private string ParseDigitalCertificateElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (40 < id.Id.Length) - { - this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40)); - - // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate - // currently have no usage in merge modules. - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiDigitalCertificateSymbol(sourceLineNumbers, id) - { - CertData = sourceFile - }); - } - - return id.Id; - } - - /// - /// Parses an digital signature element. - /// - /// Element to parse. - /// Disk id inherited from parent media. - private void ParseDigitalSignatureElement(XElement node, string diskId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string certificateId = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // sanity check for debug to ensure the stream name will not be a problem - if (null != sourceFile) - { - Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DigitalCertificate": - certificateId = this.ParseDigitalCertificateElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == certificateId) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiDigitalSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "Media", diskId)) - { - Table = "Media", - SignObject = diskId, - DigitalCertificateRef = certificateId, - Hash = sourceFile - }); - } - } - - /// - /// Parses a MajorUpgrade element. - /// - /// The element to parse. - /// The current context. - private void ParseMajorUpgradeElement(XElement node, IDictionary contextValues) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var migrateFeatures = true; - var ignoreRemoveFailure = false; - var allowDowngrades = false; - var allowSameVersionUpgrades = false; - var blockUpgrades = false; - string downgradeErrorMessage = null; - string disallowUpgradeErrorMessage = null; - string removeFeatures = null; - string schedule = null; - - var upgradeCode = contextValues["UpgradeCode"]; - if (String.IsNullOrEmpty(upgradeCode)) - { - this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "UpgradeCode", node.Name.LocalName)); - } - - var productVersion = contextValues["ProductVersion"]; - if (String.IsNullOrEmpty(productVersion)) - { - this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "Version", node.Name.LocalName)); - } - - var productLanguage = contextValues["ProductLanguage"]; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AllowDowngrades": - allowDowngrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "AllowSameVersionUpgrades": - allowSameVersionUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Disallow": - blockUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DowngradeErrorMessage": - downgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisallowUpgradeErrorMessage": - disallowUpgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MigrateFeatures": - migrateFeatures = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "IgnoreLanguage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - productLanguage = null; - } - break; - case "IgnoreRemoveFailure": - ignoreRemoveFailure = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "RemoveFeatures": - removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Schedule": - schedule = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true)); - } - - if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes")); - } - - if (allowDowngrades && allowSameVersionUpgrades) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes")); - } - - if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true)); - } - - if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes")); - } - - if (!this.Core.EncounteredError) - { - // create the row that performs the upgrade (or downgrade) - var symbol = this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - Remove = removeFeatures, - MigrateFeatures = migrateFeatures, - IgnoreRemoveFailures = ignoreRemoveFailure, - ActionProperty = WixUpgradeConstants.UpgradeDetectedProperty - }); - - if (allowDowngrades) - { - symbol.VersionMin = "0"; - symbol.Language = productLanguage; - symbol.VersionMinInclusive = true; - } - else - { - symbol.VersionMax = productVersion; - symbol.Language = productLanguage; - symbol.VersionMaxInclusive = allowSameVersionUpgrades; - } - - // Add launch condition that blocks upgrades - if (blockUpgrades) - { - this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) - { - Condition = WixUpgradeConstants.UpgradePreventedCondition, - Description = downgradeErrorMessage - }); - } - - // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted) - if (!allowDowngrades) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - VersionMin = productVersion, - Language = productLanguage, - OnlyDetect = true, - IgnoreRemoveFailures = ignoreRemoveFailure, - ActionProperty = WixUpgradeConstants.DowngradeDetectedProperty - }); - - this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) - { - Condition = WixUpgradeConstants.DowngradePreventedCondition, - Description = downgradeErrorMessage - }); - } - - // finally, schedule RemoveExistingProducts - string after = null; - switch (schedule) - { - case null: - case "afterInstallValidate": - after = "InstallValidate"; - break; - case "afterInstallInitialize": - after = "InstallInitialize"; - break; - case "afterInstallExecute": - after = "InstallExecute"; - break; - case "afterInstallExecuteAgain": - after = "InstallExecuteAgain"; - break; - case "afterInstallFinalize": - after = "InstallFinalize"; - break; - } - - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, SequenceTable.InstallExecuteSequence, "RemoveExistingProducts", afterAction: after); - } - } - - /// - /// Parses a media element. - /// - /// Element to parse. - /// Set to the PatchId if parsing Patch/Media element otherwise null. - private void ParseMediaElement(XElement node, string patchId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var id = CompilerConstants.IntegerNotSet; - string cabinet = null; - CompressionLevel? compressionLevel = null; - string diskPrompt = null; - string layout = null; - var patch = null != patchId; - string volumeLabel = null; - string source = null; - string symbols = null; - - var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Cabinet": - cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CompressionLevel": - compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); - break; - case "DiskPrompt": - diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined - break; - case "EmbedCab": - embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Layout": - layout = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "VolumeLabel": - volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = CompilerConstants.IllegalInteger; - } - - if (YesNoType.IllegalValue != embedCab) - { - if (YesNoType.Yes == embedCab) - { - if (null == cabinet) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes")); - } - else - { - if (62 < cabinet.Length) - { - this.Core.Write(ErrorMessages.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length)); - } - - cabinet = String.Concat("#", cabinet); - } - } - else // external cabinet file - { - // external cabinet files must use 8.3 filenames - if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet)) - { - this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); - } - } - } - - if (compressionLevel.HasValue && String.IsNullOrEmpty(cabinet)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel")); - } - - if (patch) - { - // Default Source to a form of the Patch Id if none is specified. - if (null == source) - { - source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); - } - } - - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DigitalSignature": - if (YesNoType.Yes == embedCab) - { - this.Core.Write(ErrorMessages.SignedEmbeddedCabinet(childSourceLineNumbers)); - } - else if (null == cabinet) - { - this.Core.Write(ErrorMessages.ExpectedSignedCabinetName(childSourceLineNumbers)); - } - else - { - this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - break; - case "PatchBaseline": - if (patch) - { - this.ParsePatchBaselineElement(child, id); - } - else - { - this.Core.UnexpectedElement(node, child); - } - break; - case "SymbolPath": - if (null != symbols) - { - symbols += "" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // add the row to the section - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) - { - DiskId = id, - DiskPrompt = diskPrompt, - Cabinet = cabinet, - VolumeLabel = volumeLabel, - Source = source, // the Source column is only set when creating a patch - CompressionLevel = compressionLevel, - Layout = layout - }); - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Media, id)) - { - SymbolType = SymbolPathType.Media, - SymbolId = id.ToString(CultureInfo.InvariantCulture), - SymbolPaths = symbols - }); - } - } - } - - /// - /// Parses a media template element. - /// - /// Element to parse. - /// Set to the PatchId if parsing Patch/Media element otherwise null. - private void ParseMediaTemplateElement(XElement node, string patchId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var cabinetTemplate = "cab{0}.cab"; - string diskPrompt = null; - var patch = null != patchId; - string volumeLabel = null; - int? maximumUncompressedMediaSize = null; - int? maximumCabinetSizeForLargeFileSplitting = null; - CompressionLevel? compressionLevel = null; // this defaults to 'medium' in the MSI and Burn backends - - var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "CabinetTemplate": - var authoredCabinetTemplateValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - if (!String.IsNullOrEmpty(authoredCabinetTemplateValue)) - { - cabinetTemplate = authoredCabinetTemplateValue; - } - - // Create an example cabinet name using the maximum number of cabinets supported, 999. - var exampleCabinetName = String.Format(cabinetTemplate, "###"); - if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) - { - // The example name should not match the authored template since that would nullify the - // reason for having multiple cabinets. External cabinet files must also be valid file names. - if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) - { - this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); - } - else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out. - { - this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); - } - } - break; - case "CompressionLevel": - compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); - break; - case "DiskPrompt": - diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined - this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "EmbedCab": - embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "VolumeLabel": - volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "MaximumUncompressedMediaSize": - maximumUncompressedMediaSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); - break; - case "MaximumCabinetSizeForLargeFileSplitting": - maximumCabinetSizeForLargeFileSplitting = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Compiler.MinValueOfMaxCabSizeForLargeFileSplitting, Compiler.MaxValueOfMaxCabSizeForLargeFileSplitting); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (YesNoType.Yes == embedCab) - { - cabinetTemplate = String.Concat("#", cabinetTemplate); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, 1)) - { - DiskId = 1 - }); - - this.Core.AddSymbol(new WixMediaTemplateSymbol(sourceLineNumbers) - { - CabinetTemplate = cabinetTemplate, - VolumeLabel = volumeLabel, - DiskPrompt = diskPrompt, - MaximumUncompressedMediaSize = maximumUncompressedMediaSize, - MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting, - CompressionLevel = compressionLevel - }); - - //else - //{ - // mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; - //} - - //else - //{ - // mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB) - //} - } - } - - /// - /// Parses a merge element. - /// - /// Element to parse. - /// Identifier for parent directory. - /// Disk id inherited from parent directory. - private void ParseMergeElement(XElement node, string directoryId, int diskId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var configData = String.Empty; - FileSymbolAttributes attributes = 0; - string language = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - break; - case "FileCompression": - var compress = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - attributes |= compress == YesNoType.Yes ? FileSymbolAttributes.Compressed : 0; - attributes |= compress == YesNoType.No ? FileSymbolAttributes.Uncompressed : 0; - break; - case "Language": - language = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == language) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ConfigurationData": - if (0 == configData.Length) - { - configData = this.ParseConfigurationDataElement(child); - } - else - { - configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixMergeSymbol(sourceLineNumbers, id) - { - DirectoryRef = directoryId, - SourceFile = sourceFile, - DiskId = diskId, - ConfigurationData = configData, - FileAttributes = attributes, - FeatureRef = Guid.Empty.ToString("B") - }); - - symbol.Set((int)WixMergeSymbolFields.Language, language); - } - } - - /// - /// Parses a standard directory element. - /// - /// Element to parse. - private void ParseStandardDirectoryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(id)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (!WindowsInstallerStandard.IsStandardDirectory(id)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id)))); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty); - break; - case "Directory": - this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); - break; - case "Merge": - this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a configuration data element. - /// - /// Element to parse. - /// String in format "name=value" with '%', ',' and '=' hex encoded. - private string ParseConfigurationDataElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else // need to hex encode these characters - { - name = name.Replace("%", "%25"); - name = name.Replace("=", "%3D"); - name = name.Replace(",", "%2C"); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - else // need to hex encode these characters - { - value = value.Replace("%", "%25"); - value = value.Replace("=", "%3D"); - value = value.Replace(",", "%2C"); - } - - this.Core.ParseForExtensionElements(node); - - return String.Concat(name, "=", value); - } - - /// - /// Parses a Level element. - /// - /// Element to parse. - /// Id of the parent Feature element. - private void ParseLevelElement(XElement node, string featureId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - int? level = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!level.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); - } - - if (String.IsNullOrEmpty(condition)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (CompilerConstants.IntegerNotSet == level) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); - level = CompilerConstants.IllegalInteger; - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ConditionSymbol(sourceLineNumbers) - { - FeatureRef = featureId, - Level = level.Value, - Condition = condition - }); - } - } - } - - /// - /// Parses a merge reference element. - /// - /// Element to parse. - /// Parents complex reference type. - /// Identifier for parent feature or feature group. - private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixMerge, id); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary)); - } - - /// - /// Parses a mime element. - /// - /// Element to parse. - /// Identifier for parent extension. - /// Identifier for parent component. - /// Flag if the parent element is advertised. - /// Content type if this is the default for the MIME type. - private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string classId = null; - string contentType = null; - var advertise = parentAdvertised; - var returnContentType = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Advertise": - advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Class": - classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "ContentType": - contentType = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Default": - returnContentType = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == contentType) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType")); - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - this.Core.ParseForExtensionElements(node); - - if (YesNoType.Yes == advertise) - { - if (YesNoType.Yes != parentAdvertised) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString())); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MIMESymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, contentType)) - { - ContentType = contentType, - ExtensionRef = extension, - CLSID = classId - }); - } - } - else if (YesNoType.No == advertise) - { - if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised) - { - this.Core.Write(ErrorMessages.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers)); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId); - if (null != classId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId); - } - } - - return YesNoType.Yes == returnContentType ? contentType : null; - } - - /// - /// Parses a patch property element. - /// - /// The element to parse. - /// True if parsing an patch element. - private void ParsePatchPropertyElement(XElement node, bool patch) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string company = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Company": - company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (patch) - { - // /Patch/PatchProperty goes directly into MsiPatchMetadata table - this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, company, name)) - { - Company = company, - Property = name, - Value = value - }); - } - else - { - if (null != company) - { - this.Core.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); - } - this.AddPrivateProperty(sourceLineNumbers, name, value); - } - } - - /// - /// Adds a row to the properties table. - /// - /// Source line numbers. - /// Name of the property. - /// Value of the property. - private void AddPrivateProperty(SourceLineNumber sourceLineNumbers, string name, string value) - { - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) - { - Value = value - }); - } - } - - /// - /// Parses a TargetProductCode element. - /// - /// The element to parse. - /// The id from the node. - private string ParseTargetProductCodeElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (id.Length > 0 && "*" != id) - { - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; - } - - /// - /// Parses a ReplacePatch element. - /// - /// The element to parse. - /// The id from the node. - private string ParseReplacePatchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; - } - - /// - /// Parses a symbol path element. - /// - /// The element to parse. - /// The path from the node. - private string ParseSymbolPathElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string path = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Path": - path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == path) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path")); - } - - this.Core.ParseForExtensionElements(node); - - return path; - } - - /// - /// Parses the All element under a PatchFamily. - /// - /// The element to parse. - private void ParseAllElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - // find unexpected attributes - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - this.Core.UnexpectedAttribute(node, attrib); - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - // Always warn when using the All element. - this.Core.Write(WarningMessages.AllChangesIncludedInPatch(sourceLineNumbers)); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) - { - Table = "*", - PrimaryKeys = "*", - }); - } - } - - /// - /// Parses all reference elements under a PatchFamily. - /// - /// The element to parse. - /// Table that reference was made to. - private void ParsePatchChildRefElement(XElement node, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) - { - Table = tableName, - PrimaryKeys = id - }); - } - } - - /// - /// Parses a PatchBaseline element. - /// - /// The element to parse. - /// Media index from parent element. - private void ParsePatchBaselineElement(XElement node, int? diskId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var parsedValidate = false; - var validationFlags = TransformFlags.PatchTransformDefault; - string baselineFile = null; - string updateFile = null; - string transformFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "BaselineFile": - baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpdateFile": - updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "TransformFile": - transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (27 < id.Id.Length) - { - this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); - } - - if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) - { - if (String.IsNullOrEmpty(baselineFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); - } - - if (String.IsNullOrEmpty(updateFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); - } - - if (!String.IsNullOrEmpty(transformFile)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); - } - } - else if (String.IsNullOrEmpty(transformFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Validate": - if (parsedValidate) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - else - { - this.ParseValidateElement(child, ref validationFlags); - parsedValidate = true; - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchBaselineSymbol(sourceLineNumbers, id) - { - DiskId = diskId ?? 1, - ValidationFlags = validationFlags, - BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, - UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, - TransformFile = new IntermediateFieldPathValue { Path = transformFile }, - }); - } - } - - /// - /// Parses a Validate element. - /// - /// The element to parse. - /// TransformValidation flags to use when creating the authoring patch transform. - private void ParseValidateElement(XElement node, ref TransformFlags validationFlags) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ProductId": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ValidateProduct; - } - else - { - validationFlags &= ~TransformFlags.ValidateProduct; - } - break; - case "ProductLanguage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ValidateLanguage; - } - else - { - validationFlags &= ~TransformFlags.ValidateLanguage; - } - break; - case "ProductVersion": - var check = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - validationFlags &= ~TransformFlags.ProductVersionMask; - switch (check) - { - case "Major": - case "major": - validationFlags |= TransformFlags.ValidateMajorVersion; - break; - case "Minor": - case "minor": - validationFlags |= TransformFlags.ValidateMinorVersion; - break; - case "Update": - case "update": - validationFlags |= TransformFlags.ValidateUpdateVersion; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update")); - break; - } - break; - case "ProductVersionOperator": - var op = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - validationFlags &= ~TransformFlags.ProductVersionOperatorMask; - switch (op) - { - case "Lesser": - case "lesser": - validationFlags |= TransformFlags.ValidateNewLessBaseVersion; - break; - case "LesserOrEqual": - case "lesserOrEqual": - validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion; - break; - case "Equal": - case "equal": - validationFlags |= TransformFlags.ValidateNewEqualBaseVersion; - break; - case "GreaterOrEqual": - case "greaterOrEqual": - validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion; - break; - case "Greater": - case "greater": - validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater")); - break; - } - break; - case "UpgradeCode": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ValidateUpgradeCode; - } - else - { - validationFlags &= ~TransformFlags.ValidateUpgradeCode; - } - break; - case "IgnoreAddExistingRow": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorAddExistingRow; - } - else - { - validationFlags &= ~TransformFlags.ErrorAddExistingRow; - } - break; - case "IgnoreAddExistingTable": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorAddExistingTable; - } - else - { - validationFlags &= ~TransformFlags.ErrorAddExistingTable; - } - break; - case "IgnoreDeleteMissingRow": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorDeleteMissingRow; - } - else - { - validationFlags &= ~TransformFlags.ErrorDeleteMissingRow; - } - break; - case "IgnoreDeleteMissingTable": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorDeleteMissingTable; - } - else - { - validationFlags &= ~TransformFlags.ErrorDeleteMissingTable; - } - break; - case "IgnoreUpdateMissingRow": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorUpdateMissingRow; - } - else - { - validationFlags &= ~TransformFlags.ErrorUpdateMissingRow; - } - break; - case "IgnoreChangingCodePage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorChangeCodePage; - } - else - { - validationFlags &= ~TransformFlags.ErrorChangeCodePage; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - } - - private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename) - { - if (!String.IsNullOrEmpty(subdirectory)) - { - if (String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName)); - } - else - { - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); - } - } - - return directoryId; - } - } -} diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs deleted file mode 100644 index 727084eb..00000000 --- a/src/WixToolset.Core/CompilerCore.cs +++ /dev/null @@ -1,1166 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Reflection; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal enum ValueListKind - { - /// - /// A list of values with nothing before the final value. - /// - None, - - /// - /// A list of values with 'and' before the final value. - /// - And, - - /// - /// A list of values with 'or' before the final value. - /// - Or - } - - /// - /// Core class for the compiler. - /// - internal class CompilerCore - { - internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; - internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; - - // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) - private static readonly List BuiltinBundleVariables = new List( - new string[] { - "AdminToolsFolder", - "AppDataFolder", - "CommonAppDataFolder", - "CommonFiles64Folder", - "CommonFilesFolder", - "CompatibilityMode", - "Date", - "DesktopFolder", - "FavoritesFolder", - "FontsFolder", - "InstallerName", - "InstallerVersion", - "LocalAppDataFolder", - "LogonUser", - "MyPicturesFolder", - "NTProductType", - "NTSuiteBackOffice", - "NTSuiteDataCenter", - "NTSuiteEnterprise", - "NTSuitePersonal", - "NTSuiteSmallBusiness", - "NTSuiteSmallBusinessRestricted", - "NTSuiteWebServer", - "PersonalFolder", - "Privileged", - "ProgramFiles64Folder", - "ProgramFiles6432Folder", - "ProgramFilesFolder", - "ProgramMenuFolder", - "RebootPending", - "SendToFolder", - "ServicePackLevel", - "StartMenuFolder", - "StartupFolder", - "System64Folder", - "SystemFolder", - "TempFolder", - "TemplateFolder", - "TerminalServer", - "UserLanguageID", - "UserUILanguageID", - "VersionMsi", - "VersionNT", - "VersionNT64", - "WindowsFolder", - "WindowsVolume", - "WixBundleAction", - "WixBundleForcedRestartPackage", - "WixBundleElevated", - "WixBundleInstalled", - "WixBundleProviderKey", - "WixBundleTag", - "WixBundleVersion", - }); - - private static readonly List DisallowedMsiProperties = new List( - new string[] { - "ACTION", - "ADDLOCAL", - "ADDSOURCE", - "ADDDEFAULT", - "ADVERTISE", - "ALLUSERS", - "REBOOT", - "REINSTALL", - "REINSTALLMODE", - "REMOVE" - }); - - private readonly Dictionary extensions; - private readonly IParseHelper parseHelper; - private readonly Intermediate intermediate; - private readonly IMessaging messaging; - private Dictionary activeSectionCachedInlinedDirectoryIds; - private HashSet activeSectionSimpleReferences; - - /// - /// Constructor for all compiler core. - /// - /// The Intermediate object representing compiled source document. - /// - /// - /// The WiX extensions collection. - internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary extensions) - { - this.extensions = extensions; - this.parseHelper = parseHelper; - this.intermediate = intermediate; - this.messaging = messaging; - } - - /// - /// Gets the section the compiler is currently emitting symbols into. - /// - /// The section the compiler is currently emitting symbols into. - public IntermediateSection ActiveSection { get; private set; } - - /// - /// Gets whether the compiler core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError => this.messaging.EncounteredError; - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages { get; set; } - - /// - /// Add a symbol to the active section. - /// - /// Symbol to add. - public T AddSymbol(T symbol) - where T : IntermediateSymbol - { - return this.ActiveSection.AddSymbol(symbol); - } - - /// - /// Convert a bit array into an int value. - /// - /// The bit array to convert. - /// The converted int value. - public int CreateIntegerFromBitArray(BitArray bits) - { - if (32 != bits.Length) - { - throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits"); - } - - int[] intArray = new int[1]; - bits.CopyTo(intArray, 0); - - return intArray[0]; - } - - /// - /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array. - /// - /// Array of attributes that map to bits. - /// Name of attribute to check. - /// Value of attribute to check. - /// The bit array in which the bit will be set if found. - /// The offset into the bit array. - /// true if the bit was set; false otherwise. - public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset) - { - for (int i = 0; i < attributeNames.Length; i++) - { - if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal)) - { - bits.Set(i + offset, YesNoType.Yes == attributeValue); - return true; - } - } - - return false; - } - - internal void InnerTextDisallowed(XElement element) - { - this.parseHelper.InnerTextDisallowed(element); - } - - /// - /// Verifies that a filename is ambiguous. - /// - /// Filename to verify. - /// true if the filename is ambiguous; false otherwise. - public static bool IsAmbiguousFilename(string filename) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - - var tilde = filename.IndexOf('~'); - return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); - } - - /// - /// Verifies that a value is a legal identifier. - /// - /// The value to verify. - /// true if the value is an identifier; false otherwise. - public bool IsValidIdentifier(string value) - { - return this.parseHelper.IsValidIdentifier(value); - } - - /// - /// Verifies if an identifier is a valid loc identifier. - /// - /// Identifier to verify. - /// True if the identifier is a valid loc identifier. - public bool IsValidLocIdentifier(string identifier) - { - return this.parseHelper.IsValidLocIdentifier(identifier); - } - - /// - /// Verifies if a filename is a valid long filename. - /// - /// Filename to verify. - /// true if wildcards are allowed in the filename. - /// true if relative paths are allowed in the filename. - /// True if the filename is a valid long filename - public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false) - { - return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); - } - - /// - /// Verifies if a filename is a valid short filename. - /// - /// Filename to verify. - /// true if wildcards are allowed in the filename. - /// True if the filename is a valid short filename - public bool IsValidShortFilename(string filename, bool allowWildcards) - { - return this.parseHelper.IsValidShortFilename(filename, allowWildcards); - } - - /// - /// Replaces the illegal filename characters to create a legal name. - /// - /// Filename to make valid. - /// Replacement string for invalid characters in filename. - /// Valid filename. - public static string MakeValidLongFileName(string filename, char replace) - { - if (String.IsNullOrEmpty(filename)) - { - return filename; - } - - StringBuilder sb = null; - - var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); - while (found != -1) - { - if (sb == null) - { - sb = new StringBuilder(filename); - } - - sb[found] = replace; - - found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; - } - - return sb?.ToString() ?? filename; - } - - /// - /// Verifies the given string is a valid product version. - /// - /// The product version to verify. - /// True if version is a valid product version - public static bool IsValidProductVersion(string version) - { - if (!Common.IsValidBinderVariable(version)) - { - Version ver = new Version(version); - - if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build) - { - return false; - } - } - - return true; - } - - /// - /// Verifies the given string is a valid module or bundle version. - /// - /// The version to verify. - /// True if version is a valid module or bundle version. - public static bool IsValidModuleOrBundleVersion(string version) - { - return Common.IsValidFourPartVersion(version); - } - - /// - /// Creates group and ordering information. - /// - /// Source line numbers. - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of this item. - /// Identifier for this item. - /// Type of previous item, if known. - /// Identifier of previous item, if known - public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, - ComplexReferenceParentType parentType, string parentId, - ComplexReferenceChildType type, string id, - ComplexReferenceChildType previousType, string previousId) - { - if (this.EncounteredError) - { - return; - } - - if (parentType != ComplexReferenceParentType.Unknown && parentId != null) - { - this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); - } - - if (previousType != ComplexReferenceChildType.Unknown && previousId != null) - { - // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? - // TODO: Also, we could potentially include an 'Attributes' field to track things like - // 'before' vs. 'after', and explicit vs. inferred dependencies. - this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) - { - ItemType = type, - ItemIdRef = id, - DependsOnType = previousType, - DependsOnIdRef = previousId, - }); - } - } - - /// - /// Creates a version 3 name-based UUID. - /// - /// The namespace UUID. - /// The value. - /// The generated GUID for the given namespace and value. - public string CreateGuid(Guid namespaceGuid, string value) - { - return this.parseHelper.CreateGuid(namespaceGuid, value); - } - - /// - /// Creates directories using the inline directory syntax. - /// - /// Source line information. - /// Optional identifier of parent directory. - /// Optional inline syntax to override attribute's value. - /// Identifier of the leaf directory created. - public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) - { - return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); - } - - /// - /// Creates a Registry row in the active section. - /// - /// Source and line number of the current row. - /// The registry entry root. - /// The registry entry key. - /// The registry entry name. - /// The registry entry value. - /// The component which will control installation/uninstallation of the registry entry. - public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId) - { - return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true); - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol name of the simple reference. - /// The primary key of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) - { - if (!this.EncounteredError) - { - var id = String.Concat(symbolName, ":", primaryKey); - - // If this simple reference hasn't been added to the active section already, add it. - if (this.activeSectionSimpleReferences.Add(id)) - { - this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey); - } - } - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol name of the simple reference. - /// The primary keys of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) - { - if (!this.EncounteredError) - { - var joinedKeys = String.Join("/", primaryKeys); - var id = String.Concat(symbolName, ":", joinedKeys); - - // If this simple reference hasn't been added to the active section already, add it. - if (this.activeSectionSimpleReferences.Add(id)) - { - this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys); - } - } - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol definition of the simple reference. - /// The primary key of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) - { - this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey); - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol definition of the simple reference. - /// The primary keys of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) - { - this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys); - } - - /// - /// A row in the WixGroup table is added for this child node and its parent node. - /// - /// Source line information for the row. - /// Type of child's complex reference parent. - /// Id of the parenet node. - /// Complex reference type of child - /// Id of the Child Node. - public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) - { - if (!this.EncounteredError) - { - this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId); - } - } - - /// - /// Add the appropriate symbols to make sure that the given table shows up - /// in the resulting output. - /// - /// Source line numbers. - /// Name of the table to ensure existance of. - public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName) - { - if (!this.EncounteredError) - { - this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName); - } - } - - /// - /// Add the appropriate symbols to make sure that the given table shows up - /// in the resulting output. - /// - /// Source line numbers. - /// Definition of the table to ensure existance of. - public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) - { - if (!this.EncounteredError) - { - this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition); - } - } - - /// - /// Get an attribute value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. - /// The attribute's value. - public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) - { - return this.parseHelper.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); - } - - /// - /// Get a valid code page by web name or number from a string attribute. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A valid code page integer value. - public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - if (null == attribute) - { - throw new ArgumentNullException(nameof(attribute)); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - try - { - return Common.GetValidCodePage(value); - } - catch (NotSupportedException) - { - this.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - } - - return CompilerConstants.IllegalInteger; - } - - /// - /// Get a valid code page by web name or number from a string attribute. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Whether to allow Unicode (UCS) or UTF code pages. - /// A valid code page integer value or variable expression. - public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false) - { - if (null == attribute) - { - throw new ArgumentNullException(nameof(attribute)); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - // Allow for localization of code page names and values. - if (this.IsValidLocIdentifier(value)) - { - return value; - } - - try - { - var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers); - return codePage.ToString(CultureInfo.InvariantCulture); - } - catch (NotSupportedException) - { - // Not a valid windows code page. - this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - } - catch (WixException e) - { - this.messaging.Write(e.Error); - } - - return null; - } - - /// - /// Get an integer attribute value and displays an error for an illegal integer value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's integer value or a special value if an error occurred during conversion. - public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - return this.parseHelper.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum); - } - - /// - /// Get a long integral attribute value and displays an error for an illegal long value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's long value or a special value if an error occurred during conversion. - public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) - { - return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum); - } - - /// - /// Get a date time attribute value and display errors for illegal values. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Int representation of the date time. - public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - string value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - try - { - DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat); - - return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) + - (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2); - } - catch (ArgumentOutOfRangeException) - { - this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (FormatException) - { - this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (OverflowException) - { - this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return CompilerConstants.IllegalInteger; - } - - /// - /// Get an integer attribute value or localize variable and displays an error for - /// an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion. - public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value)) - { - return value; - } - else - { - try - { - var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); - - if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) - { - this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); - } - else if (minimum > integer || maximum < integer) - { - this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); - integer = CompilerConstants.IllegalInteger; - } - - return value; - } - catch (FormatException) - { - this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (OverflowException) - { - this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - } - - return null; - } - - /// - /// Get a guid attribute value and displays an error for an illegal guid value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Determines whether the guid can be automatically generated. - /// If true, no error is raised on empty value. If false, an error is raised. - /// The attribute's guid value or a special value if an error occurred. - public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) - { - return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty); - } - - /// - /// Get an identifier attribute value and displays an error for an illegal identifier value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's identifier value or a special value if an error occurred. - public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute); - } - - /// - /// Get an identifier attribute value and displays an error for an illegal identifier value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's identifier value or a special value if an error occurred. - public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a yes/no value and displays an error for an illegal yes/no value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's YesNoType value. - public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a yes/no/default value and displays an error for an illegal yes/no value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's YesNoDefaultType value. - public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a short filename value and displays an error for an illegal short filename value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// true if wildcards are allowed in the filename. - /// The attribute's short filename value. - public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value)) - { - this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else if (CompilerCore.IsAmbiguousFilename(value)) - { - this.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return value; - } - - /// - /// Gets a long filename value and displays an error for an illegal long filename value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// true if wildcards are allowed in the filename. - /// true if relative paths are allowed in the filename. - /// The attribute's long filename value. - public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false) - { - return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative); - } - - /// - /// Gets a version value or possibly a binder variable and displays an error for an illegal version value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's version value. - public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Whether HKMU is returned as -1 (true), or treated as an error (false). - /// The attribute's RegisitryRootType value. - public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) - { - return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu); - } - - /// - /// Gets a Bundle variable value and displays an error for an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's value. - public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - string value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - if (CompilerCore.BuiltinBundleVariables.Contains(value)) - { - string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables); - this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues)); - } - } - - return value; - } - - /// - /// Gets an MsiProperty name value and displays an error for an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's value. - public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - string value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - if (CompilerCore.DisallowedMsiProperties.Contains(value)) - { - string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties); - this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues)); - } - } - - return value; - } - - /// - /// Checks if the string contains a property (i.e. "foo[Property]bar") - /// - /// String to evaluate for properties. - /// True if a property is found in the string. - public bool ContainsProperty(string possibleProperty) - { - return this.parseHelper.ContainsProperty(possibleProperty); - } - - /// - /// Generate an identifier by hashing data from the row. - /// - /// Three letter or less prefix for generated row identifier. - /// Information to hash. - /// The generated identifier. - public Identifier CreateIdentifier(string prefix, params string[] args) - { - return this.parseHelper.CreateIdentifier(prefix, args); - } - - /// - /// Create an identifier based on passed file name - /// - /// File name to generate identifer from - /// - public Identifier CreateIdentifierFromFilename(string filename) - { - return this.parseHelper.CreateIdentifierFromFilename(filename); - } - - /// - /// Attempts to use an extension to parse the attribute. - /// - /// Element containing attribute to be parsed. - /// Attribute to be parsed. - /// Extra information about the context in which this element is being parsed. - public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary context = null) - { - this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context); - } - - /// - /// Attempts to use an extension to parse the element. - /// - /// Element containing element to be parsed. - /// Element to be parsed. - /// Extra information about the context in which this element is being parsed. - public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary context = null) - { - this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); - } - - /// - /// Process all children of the element looking for extensions and erroring on the unexpected. - /// - /// Element to parse children. - public void ParseForExtensionElements(XElement element) - { - this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element); - } - - /// - /// Attempts to use an extension to parse the element, with support for setting component keypath. - /// - /// Element containing element to be parsed. - /// Element to be parsed. - /// Extra information about the context in which this element is being parsed. - public IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary context) - { - return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); - } - - /// - /// Displays an unexpected attribute error if the attribute is not the namespace attribute. - /// - /// Element containing unexpected attribute. - /// The unexpected attribute. - public void UnexpectedAttribute(XElement element, XAttribute attribute) - { - this.parseHelper.UnexpectedAttribute(element, attribute); - } - - /// - /// Display an unexepected element error. - /// - /// The parent element. - /// The unexpected child element. - public void UnexpectedElement(XElement parentElement, XElement childElement) - { - this.parseHelper.UnexpectedElement(parentElement, childElement); - } - - /// - /// Sends a message. - /// - /// Message to write. - public void Write(Message message) - { - this.messaging.Write(message); - } - - /// - /// Verifies that the calling assembly version is equal to or newer than the given . - /// - /// Source line information about the owner element. - /// The version required of the calling assembly. - internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion) - { - // an null or empty string means any version will work - if (!String.IsNullOrEmpty(requiredVersion)) - { - Assembly caller = Assembly.GetCallingAssembly(); - AssemblyName name = caller.GetName(); - FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location); - - Version versionRequired = new Version(requiredVersion); - Version versionCurrent = new Version(fv.FileVersion); - - if (versionRequired > versionCurrent) - { - if (this.GetType().Assembly.Equals(caller)) - { - this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired)); - } - else - { - this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name)); - } - } - } - } - - /// - /// Creates a new section and makes it the active section in the core. - /// - /// Unique identifier for the section. - /// Type of section to create. - /// Unique identifier for the compilation. - /// New section. - internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) - { - this.ActiveSection = this.CreateSection(id, type, compilationId); - - this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); - this.activeSectionSimpleReferences = new HashSet(); - - return this.ActiveSection; - } - - /// - /// Creates a new section. - /// - /// Unique identifier for the section. - /// Type of section to create. - /// Unique identifier for the compilation. - /// New section. - internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) - { - var section = new IntermediateSection(id, type, compilationId); - - this.intermediate.AddSection(section); - - return section; - } - - /// - /// Creates WixComplexReference and WixGroup rows in the active section. - /// - /// Source line information. - /// The parent type. - /// The parent id. - /// The parent language. - /// The child type. - /// The child id. - /// Whether the child is primary. - public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) - { - this.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary); - } - - /// - /// Creates a directory row from a name. - /// - /// Source line information. - /// Optional identifier for the new row. - /// Optional identifier for the parent row. - /// Long name of the directory. - /// Optional short name of the directory. - /// Optional source name for the directory. - /// Optional short source name for the directory. - /// Identifier for the newly created row. - internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) - { - return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); - } - - public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) - { - this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); - } - - internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) - { - return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); - } - - private static string CreateValueList(ValueListKind kind, IEnumerable values) - { - // Ideally, we could denote the list kind (and the list itself) directly in the - // message XML, and detect and expand in the MessageHandler.GenerateMessageString() - // method. Doing so would make vararg-style messages much easier, but impacts - // every single message we format. For now, callers just have to know when a - // message takes a list of values in a single string argument, the caller will - // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge - // that the list is an 'and' or 'or' list.) - - // For a localizable solution, we need to be able to get the list format string - // from resources. We aren't currently localized right now, so the values are - // just hard-coded. - const string valueFormat = "'{0}'"; - const string valueSeparator = ", "; - string terminalTerm = String.Empty; - - switch (kind) - { - case ValueListKind.None: - terminalTerm = ""; - break; - case ValueListKind.And: - terminalTerm = "and "; - break; - case ValueListKind.Or: - terminalTerm = "or "; - break; - } - - StringBuilder list = new StringBuilder(); - - // This weird construction helps us determine when we're adding the last value - // to the list. Instead of adding them as we encounter them, we cache the current - // value and append the *previous* one. - string previousValue = null; - bool haveValues = false; - foreach (string value in values) - { - if (null != previousValue) - { - if (haveValues) - { - list.Append(valueSeparator); - } - list.AppendFormat(valueFormat, previousValue); - haveValues = true; - } - - previousValue = value; - } - - // If we have no previous value, that means that the list contained no values, and - // something has gone very wrong. - Debug.Assert(null != previousValue); - if (null != previousValue) - { - if (haveValues) - { - list.Append(valueSeparator); - list.Append(terminalTerm); - } - list.AppendFormat(valueFormat, previousValue); - haveValues = true; - } - - return list.ToString(); - } - } -} diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs deleted file mode 100644 index 10646dfd..00000000 --- a/src/WixToolset.Core/CompilerErrors.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - - internal static class CompilerErrors - { - public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars) - { - return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars); - } - - public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue) - { - return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue); - } - - public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) - { - return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); - } - - public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) - { - return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - IllegalCharactersInProvider = 5400, - ReservedValue = 5401, - - IllegalName = 6601, - ExampleRegid = 6602, - } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. - } -} diff --git a/src/WixToolset.Core/CompilerWarnings.cs b/src/WixToolset.Core/CompilerWarnings.cs deleted file mode 100644 index 5c11b878..00000000 --- a/src/WixToolset.Core/CompilerWarnings.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - - internal static class CompilerWarnings - { - public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) - { - return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId); - } - - public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) - { - return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId); - } - - public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); - } - - public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id) - { - return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id); - } - - public static Message PropertyRemoved(string name) - { - return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name); - } - - public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id) - { - return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id); - } - - public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id) - { - return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id); - } - - public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId) - { - return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - ProvidesKeyNotFound = 5431, - RequiresKeyNotFound = 5432, - PropertyRemoved = 5433, - DiscouragedVersionAttribute = 5434, - Win64Component = 5435, - DirectoryRefStandardDirectoryDeprecated = 5436, - DefiningStandardDirectoryDeprecated = 5437, - } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. - } -} diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs deleted file mode 100644 index 6d2e75f7..00000000 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ /dev/null @@ -1,3266 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName); - private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName); - private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName); - - /// - /// Parses an ApprovedExeForElevation element. - /// - /// Element to parse - private void ParseApprovedExeForElevation(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string valueName = null; - var win64 = this.Context.IsCurrentPlatform64Bit; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - win64 = false; - break; - case "always64": - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - var attributes = WixApprovedExeForElevationAttributes.None; - - if (win64) - { - attributes |= WixApprovedExeForElevationAttributes.Win64; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id) - { - Key = key, - ValueName = valueName, - Attributes = attributes, - }); - } - } - - /// - /// Parses a Bundle element. - /// - /// Element to parse - private void ParseBundleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string copyright = null; - string aboutUrl = null; - var compressed = YesNoDefaultType.Default; - WixBundleAttributes attributes = 0; - string helpTelephone = null; - string helpUrl = null; - string manufacturer = null; - string name = null; - string tag = null; - string updateUrl = null; - string upgradeCode = null; - string version = null; - string condition = null; - string parentName = null; - - string fileSystemSafeBundleName = null; - string logVariablePrefixAndExtension = null; - string iconSourceFile = null; - string splashScreenSourceFile = null; - - // Process only standard attributes until the active section is initialized. - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AboutUrl": - aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Copyright": - copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisableModify": - var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (value) - { - case "button": - attributes |= WixBundleAttributes.SingleChangeUninstallButton; - break; - case "yes": - attributes |= WixBundleAttributes.DisableModify; - break; - case "no": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); - break; - } - break; - case "DisableRemove": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixBundleAttributes.DisableRemove; - } - break; - case "HelpTelephone": - helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HelpUrl": - helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "IconSourceFile": - iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ParentName": - parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ProviderKey": - // This can't be processed until we create the section. - break; - case "SplashScreenSourceFile": - splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Tag": - tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpdateUrl": - updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (String.IsNullOrEmpty(version)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) - { - this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); - } - - if (String.IsNullOrEmpty(upgradeCode)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); - } - - if (String.IsNullOrEmpty(copyright)) - { - if (String.IsNullOrEmpty(manufacturer)) - { - copyright = "Copyright (c). All rights reserved."; - } - else - { - copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); - } - } - - if (String.IsNullOrEmpty(name)) - { - logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log"); - } - else - { - // Ensure only allowable path characters are in "name" (and change spaces to underscores). - fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); - logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); - } - - this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; - this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId); - - // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ProviderKey": - this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); - break; - // Unknown attributes were reported earlier. - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var baSeen = false; - var chainSeen = false; - var logSeen = false; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ApprovedExeForElevation": - this.ParseApprovedExeForElevation(child); - break; - case "BootstrapperApplication": - if (baSeen) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); - } - this.ParseBootstrapperApplicationElement(child); - baSeen = true; - break; - case "BootstrapperApplicationRef": - this.ParseBootstrapperApplicationRefElement(child); - break; - case "BundleCustomData": - this.ParseBundleCustomDataElement(child); - break; - case "BundleCustomDataRef": - this.ParseBundleCustomDataRefElement(child); - break; - case "BundleExtension": - this.ParseBundleExtensionElement(child); - break; - case "BundleExtensionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); - break; - case "OptionalUpdateRegistration": - this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); - break; - case "Chain": - if (chainSeen) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); - } - this.ParseChainElement(child); - chainSeen = true; - break; - case "Container": - this.ParseContainerElement(child); - break; - case "ContainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer); - break; - case "Log": - if (logSeen) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); - } - logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); - logSeen = true; - break; - case "PayloadGroup": - this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads); - break; - case "PayloadGroupRef": - this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null); - break; - case "RelatedBundle": - this.ParseRelatedBundleElement(child); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetVariable": - this.ParseSetVariableElement(child); - break; - case "SetVariableRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); - break; - case "SoftwareTag": - this.ParseBundleTagElement(child); - break; - case "Update": - this.ParseUpdateElement(child); - break; - case "Variable": - this.ParseVariableElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!chainSeen) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - Version = version, - Copyright = copyright, - Name = name, - Manufacturer = manufacturer, - Attributes = attributes, - AboutUrl = aboutUrl, - HelpUrl = helpUrl, - HelpTelephone = helpTelephone, - UpdateUrl = updateUrl, - Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, - IconSourceFile = iconSourceFile, - SplashScreenSourceFile = splashScreenSourceFile, - Condition = condition, - Tag = tag, - Platform = this.CurrentPlatform, - ParentName = parentName, - }); - - if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) - { - var split = logVariablePrefixAndExtension.Split(':'); - symbol.LogPathVariable = split[0]; - symbol.LogPrefix = split[1]; - symbol.LogExtension = split[2]; - } - - if (null != upgradeCode) - { - this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) - { - BundleId = upgradeCode, - Action = RelatedBundleActionType.Upgrade, - }); - } - - this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) - { - Name = "bundle-attached.cab", - Type = ContainerType.Attached, - }); - - // Ensure that the bundle stores the well-known persisted values. - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME)) - { - Hidden = false, - Persisted = true, - }); - - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) - { - Hidden = false, - Persisted = true, - }); - - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) - { - Hidden = false, - Persisted = true, - }); - - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) - { - Hidden = false, - Persisted = true, - }); - } - } - - /// - /// Parse a Container element. - /// - /// Element to parse - /// - private string ParseLogElement(XElement node, string fileSystemSafeBundleName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var disableLog = YesNoType.NotSet; - var variable = "WixBundleLog"; - var logPrefix = fileSystemSafeBundleName ?? "Setup"; - var logExtension = ".log"; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Disable": - disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "PathVariable": - variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Prefix": - logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Extension": - logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!logExtension.StartsWith(".", StringComparison.Ordinal)) - { - logExtension = String.Concat(".", logExtension); - } - - this.Core.ParseForExtensionElements(node); - - return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); - } - - /// - /// Parse a Container element. - /// - /// Element to parse - private void ParseContainerElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string downloadUrl = null; - string name = null; - var type = ContainerType.Detached; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - if (id?.Id == BurnConstants.BurnUXContainerName || id?.Id == BurnConstants.BurnDefaultAttachedContainerName) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - break; - case "DownloadUrl": - downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Type": - var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeString) - { - case "attached": - type = ContainerType.Attached; - break; - case "detached": - type = ContainerType.Detached; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - else if (null == name) - { - name = id.Id; - } - - if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PackageGroupRef": - this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id) - { - Name = name, - Type = type, - DownloadUrl = downloadUrl - }); - } - } - - /// - /// Parse the BoostrapperApplication element. - /// - /// Element to parse - private void ParseBootstrapperApplicationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - Identifier previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "BootstrapperApplicationDll": - previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (id != null) - { - this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parse the BoostrapperApplication element. - /// - /// Element to parse - /// - /// - /// - private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) - { - Id = defaultId, - }; - var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - case "DpiAwareness": - var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (dpiAwarenessValue) - { - case "gdiScaled": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled; - break; - case "perMonitor": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor; - break; - case "perMonitorV2": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; - break; - case "system": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System; - break; - case "unaware": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - extensionAttributes.Add(attrib); - } - } - - compilerPayload.FinishCompilingPayload(); - - // Now that the Id is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); - this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) - { - Name = "bundle-ux.cab", - Type = ContainerType.Attached - }); - - this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) - { - DpiAwareness = dpiAwareness, - }); - } - - return compilerPayload.Id; - } - - /// - /// Parse the BoostrapperApplicationRef element. - /// - /// Element to parse - private void ParseBootstrapperApplicationRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - Identifier previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (String.IsNullOrEmpty(id)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id); - } - } - - - - /// - /// Parses a BundleCustomData element. - /// - /// Element to parse. - private void ParseBundleCustomDataElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string customDataId = null; - WixBundleCustomDataType? customDataType = null; - string extensionId = null; - var attributeDefinitions = new List(); - var foundAttributeDefinitions = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "bootstrapperApplication": - customDataType = WixBundleCustomDataType.BootstrapperApplication; - break; - case "bundleExtension": - customDataType = WixBundleCustomDataType.BundleExtension; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); - customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. - break; - } - break; - case "ExtensionId": - extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == customDataId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - var hasExtensionId = null != extensionId; - if (!customDataType.HasValue) - { - customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; - } - - if (!customDataType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); - } - else if (hasExtensionId) - { - if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); - } - } - else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "BundleAttributeDefinition": - foundAttributeDefinitions = true; - - var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); - if (attributeDefinition != null) - { - attributeDefinitions.Add(attributeDefinition); - } - break; - case "BundleElement": - this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (attributeDefinitions.Count > 0) - { - if (!this.Core.EncounteredError) - { - var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); - - this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId)) - { - AttributeNames = attributeNames, - Type = customDataType.Value, - BundleExtensionRef = extensionId, - }); - } - } - else if (!foundAttributeDefinitions) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); - } - } - - /// - /// Parses a BundleCustomDataRef element. - /// - /// Element to parse. - private void ParseBundleCustomDataRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string customDataId = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == customDataId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "BundleElement": - this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a BundleAttributeDefinition element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// BundleCustomData Id. - private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) - { - string attributeName = null; - - foreach (var attrib in node.Attributes()) - { - switch (attrib.Name.LocalName) - { - case "Id": - attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - - if (null == attributeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (this.Core.EncounteredError) - { - return null; - } - - var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName)) - { - CustomDataRef = customDataId, - Name = attributeName, - }); - return customDataAttribute; - } - - /// - /// Parses a BundleElement element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// BundleCustomData Id. - private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) - { - var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); - - foreach (var attrib in node.Attributes()) - { - this.Core.ParseExtensionAttribute(node, attrib); - } - - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "BundleAttribute": - string attributeName = null; - string value = null; - foreach (var attrib in child.Attributes()) - { - switch (attrib.Name.LocalName) - { - case "Id": - attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.ParseExtensionAttribute(child, attrib); - break; - } - } - - if (null == attributeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName)) - { - ElementId = elementId, - AttributeRef = attributeName, - CustomDataRef = customDataId, - Value = value, - }); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - - if (!this.Core.EncounteredError) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); - } - } - - /// - /// Parse the BundleExtension element. - /// - /// Element to parse - private void ParseBundleExtensionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); - Identifier previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - extensionAttributes.Add(attrib); - } - } - - compilerPayload.FinishCompilingPayload(); - - // Now that the Id is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); - previousId = compilerPayload.Id; - previousType = ComplexReferenceChildType.Payload; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // Add the BundleExtension. - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) - { - PayloadRef = compilerPayload.Id.Id, - }); - } - } - - /// - /// Parse the OptionalUpdateRegistration element. - /// - /// The element to parse. - /// The manufacturer. - /// The product family. - /// The bundle name. - private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) - { - const string defaultClassification = "Update"; - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string manufacturer = null; - string department = null; - string productFamily = null; - string name = null; - var classification = defaultClassification; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Department": - department = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ProductFamily": - productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Classification": - classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(manufacturer)) - { - if (!String.IsNullOrEmpty(defaultManufacturer)) - { - manufacturer = defaultManufacturer; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); - } - } - - if (String.IsNullOrEmpty(productFamily)) - { - if (!String.IsNullOrEmpty(defaultProductFamily)) - { - productFamily = defaultProductFamily; - } - } - - if (String.IsNullOrEmpty(name)) - { - if (!String.IsNullOrEmpty(defaultName)) - { - name = defaultName; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); - } - } - - if (String.IsNullOrEmpty(classification)) - { - this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers) - { - Manufacturer = manufacturer, - Department = department, - ProductFamily = productFamily, - Name = name, - Classification = classification - }); - } - } - - /// - /// Parse Payload element. - /// - /// Element to parse - /// ComplexReferenceParentType of parent element. (BA or PayloadGroup) - /// Identifier of parent element. - /// - /// - private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) - { - Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Compressed": - compilerPayload.ParseCompressed(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - case "DownloadUrl": - compilerPayload.ParseDownloadUrl(attrib); - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - extensionAttributes.Add(attrib); - } - } - - compilerPayload.FinishCompilingPayload(); - - // Now that the PayloadId is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child, context); - } - } - - compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); - - return compilerPayload.Id; - } - - /// - /// Parse PayloadGroup element. - /// - /// Element to parse - /// Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup) - /// Identifier of parent element. - private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId) - { - Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - var previousType = ComplexReferenceChildType.Unknown; - Identifier previousId = null; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - WixBundlePackageType? packageType = null; - switch (child.Name.LocalName) - { - case "ExePackagePayload": - packageType = WixBundlePackageType.Exe; - break; - case "MsiPackagePayload": - packageType = WixBundlePackageType.Msi; - break; - case "MspPackagePayload": - packageType = WixBundlePackageType.Msp; - break; - case "MsuPackagePayload": - packageType = WixBundlePackageType.Msu; - break; - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - - if (packageType.HasValue) - { - var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); - var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); - if (payloadSymbol != null) - { - previousId = payloadSymbol.Id; - previousType = ComplexReferenceChildType.Payload; - - this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); - } - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); - } - } - - /// - /// Parses a payload group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element (BA or PayloadGroup). - /// Identifier of parent element. - /// - /// - private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) - { - Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); - - return id; - } - - /// - /// Parse ExitCode element. - /// - /// Element to parse - /// Id of parent element - private void ParseExitCodeElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var value = CompilerConstants.IntegerNotSet; - var behavior = ExitCodeBehaviorType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); - break; - case "Behavior": - var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (behaviorString) - { - case "error": - behavior = ExitCodeBehaviorType.Error; - break; - case "forceReboot": - behavior = ExitCodeBehaviorType.ForceReboot; - break; - case "scheduleReboot": - behavior = ExitCodeBehaviorType.ScheduleReboot; - break; - case "success": - behavior = ExitCodeBehaviorType.Success; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); - behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below. - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (ExitCodeBehaviorType.NotSet == behavior) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers) - { - ChainPackageId = packageId, - Code = value, - Behavior = behavior - }); - } - } - - /// - /// Parse Chain element. - /// - /// Element to parse - private void ParseChainElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var attributes = WixChainAttributes.None; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "DisableRollback": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixChainAttributes.DisableRollback; - } - break; - case "DisableSystemRestore": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixChainAttributes.DisableSystemRestore; - } - break; - case "ParallelCache": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixChainAttributes.ParallelCache; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - string previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MsiPackage": - previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MspPackage": - previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MsuPackage": - previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "ExePackage": - previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "RollbackBoundary": - previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "PackageGroupRef": - previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.PackageGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (null == previousId) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers) - { - Attributes = attributes - }); - } - } - - /// - /// Parse MsiPackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); - } - - /// - /// Parse MspPackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); - } - - /// - /// Parse MsuPackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); - } - - /// - /// Parse ExePackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); - } - - /// - /// Parse RollbackBoundary element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var vital = YesNoType.Yes; - var transaction = YesNoType.No; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - if (id?.Id == BurnConstants.BundleDefaultBoundaryId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - break; - case "Vital": - vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Transaction": - transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - // Save the extension attributes for later... - extensionAttributes.Add(attrib); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(previousId)) - { - id = this.Core.CreateIdentifier("rba", previousId); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - - // Now that the rollback identifier is known, we can parse the extension attributes... - var contextValues = new Dictionary - { - ["RollbackBoundaryId"] = id.Id - }; - foreach (var attribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, attribute, contextValues); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); - } - - return id.Id; - } - - /// - /// Parses one of the ChainPackage elements - /// - /// Element to parse - /// Type of package to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - /// This method contains the shared logic for parsing all of the ChainPackage - /// types, as there is more in common between them than different. - private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) - { - IsRequired = false, - }; - string after = null; - string installCondition = null; - var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. - string cacheId = null; - string description = null; - string displayName = null; - var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; - var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; - var permanent = YesNoType.NotSet; - var visible = YesNoType.NotSet; - var vital = YesNoType.Yes; - string installArguments = null; - string repairArguments = null; - string uninstallArguments = null; - var perMachine = YesNoDefaultType.NotSet; - string detectCondition = null; - string protocol = null; - var installSize = CompilerConstants.IntegerNotSet; - string msuKB = null; - var enableFeatureSelection = YesNoType.NotSet; - var forcePerMachine = YesNoType.NotSet; - CompilerPayload childPackageCompilerPayload = null; - var slipstream = YesNoType.NotSet; - var hasPayloadInfo = false; - - var expectedNetFx4Args = new string[] { "/q", "/norestart" }; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - hasPayloadInfo = true; - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - hasPayloadInfo = true; - break; - case "DownloadUrl": - compilerPayload.ParseDownloadUrl(attrib); - hasPayloadInfo = true; - break; - case "After": - after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "InstallCondition": - installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Cache": - var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (value) - { - case "always": - cache = YesNoAlwaysType.Always; - break; - case "yes": - cache = YesNoAlwaysType.Yes; - break; - case "no": - cache = YesNoAlwaysType.No; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); - break; - } - break; - case "CacheId": - cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EnableFeatureSelection": - enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; - case "ForcePerMachine": - forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; - case "LogPathVariable": - logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "RollbackLogPathVariable": - rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Permanent": - permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Visible": - visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; - case "Vital": - vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "InstallArguments": - installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "RepairArguments": - repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "UninstallArguments": - uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "PerMachine": - perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); - break; - case "DetectCondition": - detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); - break; - case "Protocol": - protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "InstallSize": - installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "KB": - msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msu); - break; - case "Compressed": - compilerPayload.ParseCompressed(attrib); - hasPayloadInfo = true; - break; - case "Slipstream": - slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msp); - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - // Save the extension attributes for later... - extensionAttributes.Add(attrib); - } - } - - // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. - var packagePayloadElementName = packageType + "PackagePayload"; - foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - if (childPackageCompilerPayload != null) - { - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - else if (hasPayloadInfo) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); - } - - childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); - } - - if (compilerPayload.Id == null && childPackageCompilerPayload != null) - { - compilerPayload.Id = childPackageCompilerPayload.Id; - } - - compilerPayload.FinishCompilingPackage(); - var id = compilerPayload.Id; - - if (id.Id == BurnConstants.BundleDefaultBoundaryId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - - if (null == logPathVariable) - { - logPathVariable = String.Concat("WixBundleLog_", id.Id); - } - - if (null == rollbackPathVariable) - { - rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); - } - - if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); - } - - if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) - { - foreach (var expectedArgument in expectedNetFx4Args) - { - if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); - } - - if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); - } - - if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); - } - } - } - - // Only set default scope for EXEs and MSPs if not already set. - if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) - { - perMachine = YesNoDefaultType.Default; - } - - // Detect condition is recommended or required for Exe and Msu packages - // (depending on whether uninstall arguments were provided). - if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) - { - if (String.IsNullOrEmpty(uninstallArguments)) - { - this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); - } - } - - // Now that the package ID is known, we can parse the extension attributes... - var contextValues = new Dictionary() { { "PackageId", id.Id } }; - foreach (var attribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, attribute, contextValues); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var allowed = true; - switch (child.Name.LocalName) - { - case "SlipstreamMsp": - allowed = (packageType == WixBundlePackageType.Msi); - if (allowed) - { - this.ParseSlipstreamMspElement(child, id.Id); - } - break; - case "MsiProperty": - allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); - if (allowed) - { - this.ParseMsiPropertyElement(child, id.Id); - } - break; - case "Payload": - this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); - break; - case "PayloadGroupRef": - this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); - break; - case "Provides": - this.ParseProvidesElement(child, packageType, id.Id, out _); - break; - case "ExitCode": - allowed = (packageType == WixBundlePackageType.Exe); - if (allowed) - { - this.ParseExitCodeElement(child, id.Id); - } - break; - case "CommandLine": - allowed = (packageType == WixBundlePackageType.Exe); - if (allowed) - { - this.ParseCommandLineElement(child, id.Id); - } - break; - case "ExePackagePayload": - case "MsiPackagePayload": - case "MspPackagePayload": - case "MsuPackagePayload": - allowed = packagePayloadElementName == child.Name.LocalName; - // Handled previously - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedElement(node, child); - } - } - else - { - var context = new Dictionary() { { "Id", id.Id } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError) - { - var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); - if (packageCompilerPayload != null) - { - var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); - - this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); - } - - this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); - - WixBundlePackageAttributes attributes = 0; - attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; - attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; - - var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) - { - Type = packageType, - Attributes = attributes, - InstallCondition = installCondition, - CacheId = cacheId, - Description = description, - DisplayName = displayName, - LogPathVariable = logPathVariable, - RollbackLogPathVariable = rollbackPathVariable, - }); - - if (YesNoAlwaysType.NotSet != cache) - { - chainPackageSymbol.Cache = cache; - } - - if (YesNoType.NotSet != vital) - { - chainPackageSymbol.Vital = (vital == YesNoType.Yes); - } - - if (YesNoDefaultType.NotSet != perMachine) - { - chainPackageSymbol.PerMachine = perMachine; - } - - if (CompilerConstants.IntegerNotSet != installSize) - { - chainPackageSymbol.InstallSize = installSize; - } - - switch (packageType) - { - case WixBundlePackageType.Exe: - this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id) - { - Attributes = WixBundleExePackageAttributes.None, - DetectCondition = detectCondition, - InstallCommand = installArguments, - RepairCommand = repairArguments, - UninstallCommand = uninstallArguments, - ExeProtocol = protocol - }); - break; - - case WixBundlePackageType.Msi: - WixBundleMsiPackageAttributes msiAttributes = 0; - msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; - msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; - - this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id) - { - Attributes = msiAttributes - }); - break; - - case WixBundlePackageType.Msp: - WixBundleMspPackageAttributes mspAttributes = 0; - mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; - - this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id) - { - Attributes = mspAttributes - }); - break; - - case WixBundlePackageType.Msu: - this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id) - { - DetectCondition = detectCondition, - MsuKB = msuKB - }); - break; - } - - this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null); - } - - return id.Id; - } - - private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) - { - switch (packageType) - { - case WixBundlePackageType.Exe: - this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - - case WixBundlePackageType.Msi: - this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - - case WixBundlePackageType.Msp: - this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - - case WixBundlePackageType.Msu: - this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - } - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); - } - - private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) - { - sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) - { - Id = defaultId, - IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, - }; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Compressed": - compilerPayload.ParseCompressed(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - case "DownloadUrl": - compilerPayload.ParseDownloadUrl(attrib); - break; - case "Description": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseDescription(attrib); - } - break; - case "Hash": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseHash(attrib); - } - break; - case "ProductName": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseProductName(attrib); - } - break; - case "Size": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseSize(attrib); - } - break; - case "Version": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseVersion(attrib); - } - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - compilerPayload.FinishCompilingPackagePayload(); - - // Now that the PayloadId is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - this.Core.ParseForExtensionElements(node); - - return compilerPayload; - } - - /// - /// Parse CommandLine element. - /// - /// Element to parse - /// Parent packageId - private void ParseCommandLineElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string installArgument = null; - string uninstallArgument = null; - string repairArgument = null; - string condition = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "InstallArgument": - installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UninstallArgument": - uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "RepairArgument": - repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(condition)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers) - { - WixBundlePackageRef = packageId, - InstallArgument = installArgument, - UninstallArgument = uninstallArgument, - RepairArgument = repairArgument, - Condition = condition - }); - } - } - - /// - /// Parse PackageGroup element. - /// - /// Element to parse - private void ParsePackageGroupElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - if (id?.Id == BurnConstants.BundleChainPackageGroupId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - var previousType = ComplexReferenceChildType.Unknown; - string previousId = null; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MsiPackage": - previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MspPackage": - previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MsuPackage": - previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "ExePackage": - previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "RollbackBoundary": - previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "PackageGroupRef": - previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.PackageGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a package group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). - /// Identifier of parent element. - /// Identifier for package group element. - private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); - } - - /// - /// Parses a package group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). - /// Identifier of parent element. - /// - /// - /// Identifier for package group element. - private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string after = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - if (id == BurnConstants.BundleChainPackageGroupId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id)); - } - else - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id); - } - break; - case "After": - after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null != after && ComplexReferenceParentType.Container == parentType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); - } - - this.Core.ParseForExtensionElements(node); - - if (ComplexReferenceParentType.Container == parentType) - { - this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); - } - else - { - this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); - } - - return id; - } - - /// - /// Creates rollback boundary. - /// - /// Source line numbers. - /// Identifier for the rollback boundary. - /// Indicates whether the rollback boundary is vital or not. - /// Indicates whether the rollback boundary will use an MSI transaction. - /// Type of parent group. - /// Identifier of parent group. - /// Type of previous item, if any. - /// Identifier of previous item, if any. - private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); - - var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id)); - - if (YesNoType.NotSet != vital) - { - rollbackBoundary.Vital = (vital == YesNoType.Yes); - } - - if (YesNoType.NotSet != transaction) - { - rollbackBoundary.Transaction = (transaction == YesNoType.Yes); - } - - this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); - } - - /// - /// Creates group and ordering information for packages - /// - /// Source line numbers. - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of this item. - /// Identifier for this item. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier of explicit 'After' attribute, if given. - private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, - ComplexReferenceParentType parentType, string parentId, - ComplexReferenceChildType type, string id, - ComplexReferenceChildType previousType, string previousId, string afterId) - { - // If there's an explicit 'After' attribute, it overrides the inferred previous item. - if (null != afterId) - { - previousType = ComplexReferenceChildType.Package; - previousId = afterId; - } - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); - } - - /// - /// Parse MsiProperty element - /// - /// Element to parse - /// Id of parent element - private void ParseMsiPropertyElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string value = null; - string condition = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name)) - { - PackageRef = packageId, - Name = name, - Value = value - }); - - if (!String.IsNullOrEmpty(condition)) - { - symbol.Condition = condition; - } - } - } - - /// - /// Parse SlipstreamMsp element - /// - /// Element to parse - /// Id of parent element - private void ParseSlipstreamMspElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id)) - { - TargetPackageRef = packageId, - MspPackageRef = id - }); - } - } - - /// - /// Parse RelatedBundle element - /// - /// Element to parse - private void ParseRelatedBundleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var actionType = RelatedBundleActionType.Detect; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Action": - var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (action) - { - case "Detect": - case "detect": - actionType = RelatedBundleActionType.Detect; - break; - case "Upgrade": - case "upgrade": - actionType = RelatedBundleActionType.Upgrade; - break; - case "Addon": - case "addon": - actionType = RelatedBundleActionType.Addon; - break; - case "Patch": - case "patch": - actionType = RelatedBundleActionType.Patch; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) - { - BundleId = id, - Action = actionType, - }); - } - } - - /// - /// Parse Update element - /// - /// Element to parse - private void ParseUpdateElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string location = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Location": - location = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == location) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers) - { - Location = location - }); - } - } - - /// - /// Parse SetVariable element - /// - /// Element to parse - private void ParseSetVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string variable = null; - string condition = null; - string after = null; - string value = null; - string typeValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Variable": - variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Type": - typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib, null); - } - } - - var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); - - this.Core.ParseForExtensionElements(node); - - if (id == null) - { - id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); - } - - this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); - - if (!this.Messaging.EncounteredError) - { - this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id) - { - Value = value, - Type = type, - }); - } - } - - /// - /// Parse Variable element - /// - /// Element to parse - private void ParseVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var hidden = false; - string name = null; - var persisted = false; - string value = null; - string typeValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - hidden = true; - } - break; - case "Name": - name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); - break; - case "Persisted": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - persisted = true; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Type": - typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); - } - - if (hidden && persisted) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); - } - - var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) - { - Value = value, - Type = type, - Hidden = hidden, - Persisted = persisted - }); - } - } - - private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) - { - WixBundleVariableType type; - switch (typeValue) - { - case "formatted": - type = WixBundleVariableType.Formatted; - break; - case "numeric": - type = WixBundleVariableType.Numeric; - break; - case "string": - type = WixBundleVariableType.String; - break; - case "version": - type = WixBundleVariableType.Version; - break; - case null: - type = WixBundleVariableType.Unknown; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); - return WixBundleVariableType.Unknown; - } - - if (type != WixBundleVariableType.Unknown) - { - if (value == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); - } - - return type; - } - else if (value == null) - { - return type; - } - - // Infer the type from the current value... - if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) - { - // Version constructor does not support simple "v#" syntax so check to see if the value is - // non-negative real quick. - if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) - { - return WixBundleVariableType.Version; - } - else if (Version.TryParse(value.Substring(1), out var _)) - { - return WixBundleVariableType.Version; - } - } - - // Not a version, check for numeric. - if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) - { - return WixBundleVariableType.Numeric; - } - - return WixBundleVariableType.String; - } - } -} diff --git a/src/WixToolset.Core/Compiler_Dependency.cs b/src/WixToolset.Core/Compiler_Dependency.cs deleted file mode 100644 index 7c863883..00000000 --- a/src/WixToolset.Core/Compiler_Dependency.cs +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - // The root registry key for the dependency extension. We write to Software\Classes explicitly - // based on the current security context instead of HKCR. See - // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. - private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; - - private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' }; - - /// - /// Processes the ProviderKey bundle attribute. - /// - /// Source line number for the parent element. - /// Parent element of attribute. - /// The XML attribute for the ProviderKey attribute. - private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute) - { - var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute); - int illegalChar; - - // Make sure the key does not contain any illegal characters or values. - if (String.IsNullOrEmpty(providerKey)) - { - this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName)); - } - else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) - { - this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); - } - else if ("ALL" == providerKey) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey)); - } - - if (!this.Messaging.EncounteredError) - { - // Generate the primary key for the row. - var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey); - - // Create the provider symbol for the bundle. The Component_ field is required - // in the table definition but unused for bundles, so just set it to the valid ID. - this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) - { - ParentRef = id.Id, - ProviderKey = providerKey, - Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle, - }); - } - } - - /// - /// Processes the Provides element. - /// - /// The XML node for the Provides element. - /// The type of the package being chained into a bundle, or null if building an MSI package. - /// The identifier of the parent component or package. - /// Possible KeyPath identifier. - /// Yes if this is the keypath. - private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath) - { - possibleKeyPath = null; - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string version = null; - string displayName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Make sure the key is valid. The key will default to the ProductCode for MSI packages - // and the package code for MSP packages in the binder if not specified. - if (!String.IsNullOrEmpty(key)) - { - int illegalChar; - - // Make sure the key does not contain any illegal characters or values. - if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters))) - { - this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters))); - } - else if ("ALL" == key) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key)); - } - } - else if (!packageType.HasValue) - { - // Make sure the ProductCode is authored and set the key. - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode"); - key = "!(bind.property.ProductCode)"; - } - else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType) - { - // Must specify the provider key when authored for a package. - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - // The Version attribute should not be authored in or for an MSI package. - if (!String.IsNullOrEmpty(version)) - { - switch (packageType) - { - case null: - this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers)); - break; - case WixBundlePackageType.Msi: - this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId)); - break; - } - } - else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType) - { - // Must specify the Version when authored for packages that do not contain a version. - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - - // Need the element ID for child element processing, so generate now if not authored. - if (null == id) - { - id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Requires": - this.ParseRequiresElement(child, id.Id); - break; - case "RequiresRef": - this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Messaging.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) - { - ParentRef = parentId, - ProviderKey = key, - }); - - if (!String.IsNullOrEmpty(version)) - { - symbol.Version = version; - } - - if (!String.IsNullOrEmpty(displayName)) - { - symbol.DisplayName = displayName; - } - - if (!packageType.HasValue) - { - // Generate registry rows for the provider using binder properties. - var keyProvides = String.Concat(DependencyRegistryRoot, key); - var root = RegistryRootType.MachineUser; - - var value = "[ProductCode]"; - this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId); - - value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]"; - var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId); - - value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]"; - this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId); - - // Use the Version registry value and use that as a potential key path. - possibleKeyPath = versionRegistrySymbol.Id; - } - } - - return YesNoType.NotSet; - } - - /// - /// Processes the Requires element. - /// - /// The XML node for the Requires element. - /// The parent provider identifier. - private void ParseRequiresElement(XElement node, string providerId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string providerKey = null; - string minVersion = null; - string maxVersion = null; - var attributes = WixDependencySymbolAttributes.None; - var illegalChar = -1; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ProviderKey": - providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive; - } - break; - case "IncludeMaximum": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (null == id) - { - // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef - // element will be necessary and the Id attribute will be required. - if (!String.IsNullOrEmpty(providerId)) - { - id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey); - } - else - { - this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides")); - id = Identifier.Invalid; - } - } - - if (String.IsNullOrEmpty(providerKey)) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey")); - } - // Make sure the key does not contain any illegal characters. - else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) - { - this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); - } - - if (!this.Messaging.EncounteredError) - { - this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id) - { - ProviderKey = providerKey, - MinVersion = minVersion, - MaxVersion = maxVersion, - Attributes = attributes - }); - - // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol. - if (!String.IsNullOrEmpty(providerId)) - { - this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) - { - WixDependencyProviderRef = providerId, - WixDependencyRef = id.Id, - }); - } - } - } - - /// - /// Processes the RequiresRef element. - /// - /// The XML node for the RequiresRef element. - /// The parent provider identifier. - /// Whether the Requires custom action should be referenced. - private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(id)) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - // Create a link dependency on the row that contains information we'll need during bind. - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id); - - // Create the relationship between the WixDependency row and the parent WixDependencyProvider row. - this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) - { - WixDependencyProviderRef = providerId, - WixDependencyRef = id, - }); - } - } - } -} diff --git a/src/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/WixToolset.Core/Compiler_EmbeddedUI.cs deleted file mode 100644 index ede03933..00000000 --- a/src/WixToolset.Core/Compiler_EmbeddedUI.cs +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses an EmbeddedChaniner element. - /// - /// Element to parse. - private void ParseEmbeddedChainerElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string commandLine = null; - string condition = null; - string source = null; - var type = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "BinarySource": - if (null != source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - type = 0x2; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary - break; - case "CommandLine": - commandLine = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "FileSource": - if (null != source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - type = 0x12; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File - break; - case "PropertySource": - if (null != source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - type = 0x32; - // cannot add a reference to a Property because it may be created at runtime. - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("mec", source, type.ToString()); - } - - if (null == source) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiEmbeddedChainerSymbol(sourceLineNumbers, id) - { - Condition = condition, - CommandLine = commandLine, - Source = source, - Type = type - }); - } - } - - /// - /// Parses an EmbeddedUI element. - /// - /// Element to parse. - private void ParseEmbeddedUIElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - var supportsBasicUI = false; - var messageFilter = WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT | WindowsInstallerConstants.INSTALLLOGMODE_ERROR | WindowsInstallerConstants.INSTALLLOGMODE_WARNING | WindowsInstallerConstants.INSTALLLOGMODE_USER - | WindowsInstallerConstants.INSTALLLOGMODE_INFO | WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE | WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE - | WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA - | WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS | WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA | WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE - | WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE | WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG | WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE - | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "IgnoreFatalExit": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT; - } - break; - case "IgnoreError": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ERROR; - } - break; - case "IgnoreWarning": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_WARNING; - } - break; - case "IgnoreUser": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_USER; - } - break; - case "IgnoreInfo": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INFO; - } - break; - case "IgnoreFilesInUse": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE; - } - break; - case "IgnoreResolveSource": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE; - } - break; - case "IgnoreOutOfDiskSpace": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE; - } - break; - case "IgnoreActionStart": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART; - } - break; - case "IgnoreActionData": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA; - } - break; - case "IgnoreProgress": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS; - } - break; - case "IgnoreCommonData": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA; - } - break; - case "IgnoreInitialize": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE; - } - break; - case "IgnoreTerminate": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE; - } - break; - case "IgnoreShowDialog": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG; - } - break; - case "IgnoreRMFilesInUse": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE; - } - break; - case "IgnoreInstallStart": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART; - } - break; - case "IgnoreInstallEnd": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; - } - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SupportBasicUI": - supportsBasicUI = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(sourceFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - else if (String.IsNullOrEmpty(name)) - { - name = Path.GetFileName(sourceFile); - if (!this.Core.IsValidLongFilename(name, false)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - else if (String.IsNullOrEmpty(name)) - { - name = id.Id; - } - - if (!name.Contains(".")) - { - this.Core.Write(ErrorMessages.InvalidEmbeddedUIFileName(sourceLineNumbers, name)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "EmbeddedUIResource": - this.ParseEmbeddedUIResourceElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) - { - FileName = name, - EntryPoint = true, - SupportsBasicUI = supportsBasicUI, - MessageFilter = messageFilter, - Source = sourceFile - }); - } - } - - /// - /// Parses a embedded UI resource element. - /// - /// Element to parse. - private void ParseEmbeddedUIResourceElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(sourceFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - else if (String.IsNullOrEmpty(name)) - { - name = Path.GetFileName(sourceFile); - if (!this.Core.IsValidLongFilename(name, false)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - else if (String.IsNullOrEmpty(name)) - { - name = id.Id; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) - { - FileName = name, - Source = sourceFile - }); - } - } - } -} diff --git a/src/WixToolset.Core/Compiler_Module.cs b/src/WixToolset.Core/Compiler_Module.cs deleted file mode 100644 index 3986c8da..00000000 --- a/src/WixToolset.Core/Compiler_Module.cs +++ /dev/null @@ -1,662 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Globalization; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a module element. - /// - /// Element to parse. - private void ParseModuleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var codepage = 0; - string moduleId = null; - string version = null; - var setCodepage = false; - var setPackageName = false; - var setKeywords = false; - var ignoredForMergeModules = false; - - this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-MODULE-NAME-HERE" == this.activeName) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); - } - else - { - this.activeName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - } - break; - case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); - break; - case "Guid": - moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "InstallerVersion": - msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Language": - this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == moduleId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid")); - } - - if (null == this.activeLanguage) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - } - - if (null == version) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) - { - this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version)); - } - - try - { - this.compilingModule = true; // notice that we are actually building a Merge Module here - this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); - break; - case "Configuration": - this.ParseConfigurationElement(child); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Dependency": - this.ParseDependencyElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Exclusion": - this.ParseExclusionElement(child); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "IgnoreTable": - this.ParseIgnoreTableElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "StandardDirectory": - this.ParseStandardDirectoryElement(child); - break; - case "Substitution": - this.ParseSubstitutionElement(child); - break; - case "SummaryInformation": - this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules); - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - if (!setPackageName) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = this.activeName - }); - } - - if (!setKeywords) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = "Installer" - }); - } - - var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) - { - ModuleId = this.activeName, - Language = this.activeLanguage, - Version = version - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PackageCode, - Value = moduleId - }); - - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage); - } - } - finally - { - this.compilingModule = false; // notice that we are no longer building a Merge Module here - } - } - - /// - /// Parses a dependency element. - /// - /// Element to parse. - private void ParseDependencyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string requiredId = null; - var requiredLanguage = CompilerConstants.IntegerNotSet; - string requiredVersion = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "RequiredId": - requiredId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "RequiredLanguage": - requiredLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "RequiredVersion": - requiredVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == requiredId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId")); - requiredId = String.Empty; - } - - if (CompilerConstants.IntegerNotSet == requiredLanguage) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage")); - requiredLanguage = CompilerConstants.IllegalInteger; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ModuleDependencySymbol(sourceLineNumbers) - { - ModuleID = this.activeName, - RequiredID = requiredId, - RequiredLanguage = requiredLanguage, - RequiredVersion = requiredVersion - }); - - symbol.Set((int)ModuleDependencySymbolFields.ModuleLanguage, this.activeLanguage); - } - } - - /// - /// Parses an exclusion element. - /// - /// Element to parse. - private void ParseExclusionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string excludedId = null; - var excludeExceptLanguage = CompilerConstants.IntegerNotSet; - var excludeLanguage = CompilerConstants.IntegerNotSet; - var excludedLanguageField = "0"; - string excludedMaxVersion = null; - string excludedMinVersion = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludedId": - excludedId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ExcludeExceptLanguage": - excludeExceptLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "ExcludeLanguage": - excludeLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "ExcludedMaxVersion": - excludedMaxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ExcludedMinVersion": - excludedMinVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == excludedId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId")); - excludedId = String.Empty; - } - - if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage) - { - this.Core.Write(ErrorMessages.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers)); - } - else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage) - { - excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture); - } - else if (CompilerConstants.IntegerNotSet != excludeLanguage) - { - excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ModuleExclusionSymbol(sourceLineNumbers) - { - ModuleID = this.activeName, - ExcludedID = excludedId, - ExcludedMinVersion = excludedMinVersion, - ExcludedMaxVersion = excludedMaxVersion - }); - - symbol.Set((int)ModuleExclusionSymbolFields.ModuleLanguage, this.activeLanguage); - symbol.Set((int)ModuleExclusionSymbolFields.ExcludedLanguage, excludedLanguageField); - } - } - - /// - /// Parses a configuration element for a configurable merge module. - /// - /// Element to parse. - private void ParseConfigurationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string contextData = null; - string defaultValue = null; - string description = null; - string displayName = null; - var format = CompilerConstants.IntegerNotSet; - string helpKeyword = null; - string helpLocation = null; - bool keyNoOrphan = false; - bool nonNullable = false; - Identifier name = null; - string type = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ContextData": - contextData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DefaultValue": - defaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Format": - var formatStr = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (formatStr) - { - case "Text": - case "text": - format = 0; - break; - case "Key": - case "key": - format = 1; - break; - case "Integer": - case "integer": - format = 2; - break; - case "Bitfield": - case "bitfield": - format = 3; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield")); - break; - } - break; - case "HelpKeyword": - helpKeyword = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HelpLocation": - helpLocation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "KeyNoOrphan": - keyNoOrphan = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "NonNullable": - nonNullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Type": - type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (CompilerConstants.IntegerNotSet == format) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ModuleConfigurationSymbol(sourceLineNumbers, name) - { - Format = format, - Type = type, - ContextData = contextData, - DefaultValue = defaultValue, - KeyNoOrphan = keyNoOrphan, - NonNullable = nonNullable, - DisplayName = displayName, - Description = description, - HelpLocation = helpLocation, - HelpKeyword = helpKeyword - }); - } - } - - /// - /// Parses a substitution element for a configurable merge module. - /// - /// Element to parse. - private void ParseSubstitutionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string column = null; - string rowKeys = null; - string table = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Column": - column = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Row": - rowKeys = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Table": - table = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == column) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column")); - column = String.Empty; - } - - if (null == table) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table")); - table = String.Empty; - } - - if (null == rowKeys) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ModuleSubstitutionSymbol(sourceLineNumbers) - { - Table = table, - Row = rowKeys, - Column = column, - Value = value - }); - } - } - - /// - /// Parses an IgnoreTable element. - /// - /// Element to parse. - private void ParseIgnoreTableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ModuleIgnoreTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, id))); - } - } - } -} diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs deleted file mode 100644 index 87ccceb7..00000000 --- a/src/WixToolset.Core/Compiler_Package.cs +++ /dev/null @@ -1,4996 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a product element. - /// - /// Element to parse. - private void ParsePackageElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compressed = YesNoDefaultType.Default; - var sourceBits = 0; - string codepage = null; - var productCode = "*"; - string productLanguage = null; - var isPerMachine = true; - string upgradeCode = null; - string manufacturer = null; - string version = null; - string symbols = null; - var isCodepageSet = false; - var isPackageNameSet = false; - var isKeywordsSet = false; - var isPackageAuthorSet = false; - - this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "InstallerVersion": - msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Language": - productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); - if ("PUT-COMPANY-NAME-HERE" == manufacturer) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); - } - break; - case "Name": - this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); - if ("PUT-PRODUCT-NAME-HERE" == this.activeName) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); - } - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "Scope": - var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installScope) - { - case "perMachine": - // handled below after we create the section. - break; - case "perUser": - isPerMachine = false; - sourceBits |= 8; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); - break; - } - break; - case "ShortNames": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - sourceBits |= 1; - } - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). - var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - if (!String.IsNullOrEmpty(verifiedVersion)) - { - version = attrib.Value; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == productCode) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == manufacturer) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == upgradeCode) - { - this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); - } - - if (null == version) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidProductVersion(version)) - { - this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); - } - - if (compressed != YesNoDefaultType.No) - { - sourceBits |= 2; - } - - if (this.Core.EncounteredError) - { - return; - } - - try - { - this.compilingProduct = true; - this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId); - - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); - if (null != upgradeCode) - { - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); - } - - if (isPerMachine) - { - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); - } - - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = sourceBits.ToString(CultureInfo.InvariantCulture) - }); - - var contextValues = new Dictionary - { - ["ProductLanguage"] = productLanguage, - ["ProductVersion"] = version, - ["UpgradeCode"] = upgradeCode - }; - - var featureDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "_locDefinition": - break; - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "ComplianceCheck": - this.ParseComplianceCheckElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroup": - this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "InstanceTransforms": - this.ParseInstanceTransformsElement(child); - break; - case "Launch": - this.ParseLaunchElement(child); - break; - case "MajorUpgrade": - this.ParseMajorUpgradeElement(child, contextValues); - break; - case "Media": - this.ParseMediaElement(child, null); - break; - case "MediaTemplate": - this.ParseMediaTemplateElement(child, null); - break; - case "PackageCertificates": - case "PatchCertificates": - this.ParseCertificatesElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "SoftwareTag": - this.ParsePackageTagElement(child); - break; - case "StandardDirectory": - this.ParseStandardDirectoryElement(child); - break; - case "SummaryInformation": - this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "Upgrade": - this.ParseUpgradeElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers) - { - PackageId = productCode, - UpgradeCode = upgradeCode, - Name = this.activeName, - Language = productLanguage, - Version = version, - Manufacturer = manufacturer, - Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None, - Codepage = codepage, - }); - - if (!isPackageNameSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = this.activeName - }); - } - - if (!isPackageAuthorSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = manufacturer - }); - } - - if (!isKeywordsSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = "Installer" - }); - } - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) - { - SymbolId = productCode, - SymbolType = SymbolPathType.Product, - SymbolPaths = symbols, - }); - } - } - } - finally - { - this.compilingProduct = false; - } - } - - private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) - { - // Let's default to a modern version of MSI. Users can override, - // of course, subject to platform-specific limitations. - msiVersion = 500; - - switch (this.CurrentPlatform) - { - case Platform.X86: - platform = "Intel"; - break; - case Platform.X64: - platform = "x64"; - break; - case Platform.ARM64: - platform = "Arm64"; - break; - default: - throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); - } - } - - private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language) - { - if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) - { - msiVersion = 200; - this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); - } - - if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) - { - msiVersion = 500; - this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); - } - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Installation Database" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PlatformAndLanguage, - Value = $"{platform};{language}" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = "2" - }); - } - - /// - /// Parses an odbc driver or translator element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Default identifer for driver/translator file. - /// Symbol type we're processing for. - private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var driver = fileId; - string name = null; - var setup = fileId; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "File": - driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SetupFile": - setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("odb", name, fileId, setup); - } - - // drivers have a few possible children - if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) - { - // process any data sources for the driver - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ODBCDataSource": - string ignoredKeyPath = null; - this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); - break; - case "Property": - this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - else - { - this.Core.ParseForExtensionElements(node); - } - - if (!this.Core.EncounteredError) - { - switch (symbolDefinitionType) - { - case SymbolDefinitionType.ODBCDriver: - this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - FileRef = driver, - SetupFileRef = setup, - }); - break; - case SymbolDefinitionType.ODBCTranslator: - this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - FileRef = driver, - SetupFileRef = setup, - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); - } - } - } - - /// - /// Parses a Property element underneath an ODBC driver or translator. - /// - /// Element to parse. - /// Identifier of parent driver or translator. - /// Name of the table to create property in. - private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string propertyValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var identifier = new Identifier(AccessModifier.Section, parentId, id); - switch (symbolDefinitionType) - { - case SymbolDefinitionType.ODBCAttribute: - this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) - { - DriverRef = parentId, - Attribute = id, - Value = propertyValue, - }); - break; - case SymbolDefinitionType.ODBCSourceAttribute: - this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) - { - DataSourceRef = parentId, - Attribute = id, - Value = propertyValue, - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); - } - } - } - - /// - /// Parse an odbc data source element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Default name of driver. - /// Identifier of this element in case it is a keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var keyPath = YesNoType.NotSet; - string name = null; - var registration = CompilerConstants.IntegerNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DriverName": - driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Registration": - var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (registrationValue) - { - case "machine": - registration = 0; - break; - case "user": - registration = 1; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == registration) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); - registration = CompilerConstants.IllegalInteger; - } - - if (null == id) - { - id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Property": - this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - DriverDescription = driverName, - Registration = registration - }); - } - - possibleKeyPath = id.Id; - return keyPath; - } - - /// - /// Parses a package element. - /// - /// Element to parse. - /// - /// - /// - /// - private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string codepage = null; - string packageName = null; - string keywords = null; - string packageAuthor = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); - break; - case "Description": - packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Keywords": - keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-COMPANY-NAME-HERE" == packageAuthor) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (null != codepage) - { - isCodepageSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - } - - if (null != packageName) - { - isPackageNameSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - } - - if (null != packageAuthor) - { - isPackageAuthorSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - } - - if (null != keywords) - { - isKeywordsSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - } - } - } - - /// - /// Parses a patch information element. - /// - /// Element to parse. - private void ParsePatchInformationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var codepage = "1252"; - string comments = null; - var keywords = "Installer,Patching,PCP,Database"; - var msiVersion = 1; // Should always be 1 for patches - string packageAuthor = null; - var packageName = this.activeName; - var security = YesNoDefaultType.Default; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AdminImage": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Comments": - comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Compressed": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Description": - packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Keywords": - keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Languages": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Manufacturer": - packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Platforms": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "ReadOnly": - security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "ShortNames": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "SummaryCodepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Patch" - }); - - if (null != packageName) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - } - - if (null != packageAuthor) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - } - - if (null != keywords) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - } - - if (null != comments) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = comments - }); - } - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = "0" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" - }); - } - } - - /// - /// Parses a permission element. - /// - /// Element to parse. - /// Identifier of object to be secured. - /// Name of table that contains objectId. - private void ParsePermissionElement(XElement node, string objectId, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var bits = new BitArray(32); - string domain = null; - string[] specialPermissions = null; - string user = null; - - switch (tableName) - { - case "CreateFolder": - specialPermissions = LockPermissionConstants.FolderPermissions; - break; - case "File": - specialPermissions = LockPermissionConstants.FilePermissions; - break; - case "Registry": - specialPermissions = LockPermissionConstants.RegistryPermissions; - break; - default: - this.Core.UnexpectedElement(node.Parent, node); - return; // stop processing this element since no valid permissions are available - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Domain": - domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "User": - user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "FileAllRights": - // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) - bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; - break; - case "SpecificRightsAll": - // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) - bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; - break; - default: - var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (!this.Core.TrySetBitFromName(LockPermissionConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) - { - if (!this.Core.TrySetBitFromName(LockPermissionConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) - { - if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) - { - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == user) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); - } - - var permission = this.Core.CreateIntegerFromBitArray(bits); - - if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL - { - this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) - { - LockObject = objectId, - Table = tableName, - Domain = domain, - User = user, - Permission = permission - }); - } - } - - /// - /// Parses an extended permission element. - /// - /// Element to parse. - /// Identifier of object to be secured. - /// Name of table that contains objectId. - private void ParsePermissionExElement(XElement node, string objectId, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - Identifier id = null; - string sddl = null; - - switch (tableName) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.Core.UnexpectedElement(node.Parent, node); - return; // stop processing this element since nothing will be valid. - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Sddl": - sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == sddl) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) - { - LockObject = objectId, - Table = tableName, - SDDLText = sddl, - Condition = condition - }); - } - } - - /// - /// Parses a progid element - /// - /// Element to parse. - /// Identifier of parent component. - /// Flag if progid is advertised. - /// CLSID related to ProgId. - /// Default description of ProgId - /// Optional parent ProgId - /// Set to true if an extension is found; used for error-checking. - /// Whether or not this ProgId is the first one found in the parent class. - /// This element's Id. - private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string icon = null; - var iconIndex = CompilerConstants.IntegerNotSet; - string noOpen = null; - string progId = null; - var progIdAdvertise = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Advertise": - progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "NoOpen": - noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); - } - else if (YesNoType.NotSet != progIdAdvertise) - { - advertise = progIdAdvertise; - } - - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) - { - this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); - } - - var firstProgIdForNestedClass = YesNoType.Yes; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Extension": - this.ParseExtensionElement(child, componentId, advertise, progId); - foundExtension = true; - break; - case "ProgId": - // Only allow one nested ProgId. If we have a child, we should not have a parent. - if (null == parent) - { - if (YesNoType.Yes == advertise) - { - this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); - } - else if (YesNoType.No == advertise) - { - this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); - } - - firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (YesNoType.Yes == advertise) - { - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, progId)) - { - ProgId = progId, - ParentProgIdRef = parent, - ClassRef = classId, - Description = description, - }); - - if (null != icon) - { - symbol.IconRef = icon; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - } - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - symbol.IconIndex = iconIndex; - } - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); - } - } - else if (YesNoType.No == advertise) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); - if (null != classId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); - if (null != parent) // if this is a version independent ProgId - { - if (YesNoType.Yes == firstProgIdForClass) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); - } - else - { - if (YesNoType.Yes == firstProgIdForClass) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); - } - } - } - - if (null != icon) // ProgId's Default Icon - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); - - icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - icon = String.Concat(icon, ",", iconIndex); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); - } - } - - if (null != noOpen) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name - } - - // raise an error for an orphaned ProgId - if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) - { - this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); - } - - return progId; - } - - /// - /// Parses a property element. - /// - /// Element to parse. - private void ParsePropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var admin = false; - var complianceCheck = false; - var hidden = false; - var secure = false; - var suppressModularization = YesNoType.NotSet; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Admin": - admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ComplianceCheck": - complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Secure": - secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if ("ProductID" == id.Id) - { - this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); - } - else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) - { - this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); - } - - if ("ErrorDialog" == id.Id) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - { - switch (child.Name.LocalName) - { - case "ProductSearch": - this.ParseProductSearchElement(child, id.Id); - secure = true; - break; - default: - // let ParseSearchSignatures handle standard AppSearch children and unknown elements - break; - } - } - } - } - - this.Core.InnerTextDisallowed(node); - - // see if this property is used for appSearch - var signatures = this.ParseSearchSignatures(node); - - // If we're doing CCP then there must be a signature. - if (complianceCheck && 0 == signatures.Count) - { - this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); - } - - foreach (var sig in signatures) - { - if (complianceCheck && !this.Core.EncounteredError) - { - this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, sig))); - } - - this.AddAppSearch(sourceLineNumbers, id, sig); - } - - // If we're doing AppSearch get that setup. - if (0 < signatures.Count) - { - this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); - } - else // just a normal old property. - { - // If the property value is empty and none of the flags are set, print out a warning that we're ignoring - // the element. - if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) - { - this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); - } - else // there is a value and/or a flag set, do that. - { - this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); - } - } - - if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) - { - this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); - - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) - { - SuppressIdentifier = id.Id - }); - } - } - - /// - /// Parses a RegistryKey element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. - /// Parent key for this Registry element when nested. - /// true if the component is 64-bit. - /// Identifier of this registry key since it could be the component's keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var key = parentKey; // default to parent key path - var forceCreateOnInstall = false; - var forceDeleteOnUninstall = false; - var keyPath = YesNoType.NotSet; - - possibleKeyPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ForceCreateOnInstall": - forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ForceDeleteOnUninstall": - forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != parentKey) - { - key = Path.Combine(parentKey, key); - } - key = key?.TrimEnd('\\'); - break; - case "Root": - if (root.HasValue) - { - this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); - } - - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); - - if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present - { - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - } - else // does not generate a Registry row, so no Id should be present - { - if (null != id) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); - } - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - key = String.Empty; // set the key to something to prevent null reference exceptions - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - string possibleChildKeyPath = null; - - switch (child.Name.LocalName) - { - case "RegistryKey": - if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) - { - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - possibleKeyPath = possibleChildKeyPath; // the child is the key path - keyPath = YesNoType.Yes; - } - else if (null == possibleKeyPath && null != possibleChildKeyPath) - { - possibleKeyPath = possibleChildKeyPath; - } - break; - case "RegistryValue": - if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) - { - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - possibleKeyPath = possibleChildKeyPath; // the child is the key path - keyPath = YesNoType.Yes; - } - else if (null == possibleKeyPath && null != possibleChildKeyPath) - { - possibleKeyPath = possibleChildKeyPath; - } - break; - case "Permission": - if (!forceCreateOnInstall) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); - } - this.ParsePermissionElement(child, id.Id, "Registry"); - break; - case "PermissionEx": - if (!forceCreateOnInstall) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); - } - this.ParsePermissionExElement(child, id.Id, "Registry"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError && null != name) - { - this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - ComponentRef = componentId, - }); - } - - return keyPath; - } - - /// - /// Parses a RegistryValue element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. - /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. - /// true if the component is 64-bit. - /// Identifier of this registry key since it could be the component's keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var key = parentKey; // default to parent key path - string name = null; - string value = null; - string action = null; - var valueType = RegistryValueType.String; - var actionType = RegistryValueActionType.Write; - var keyPath = YesNoType.NotSet; - - possibleKeyPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "append": - actionType = RegistryValueActionType.Append; - break; - case "prepend": - actionType = RegistryValueActionType.Prepend; - break; - case "write": - actionType = RegistryValueActionType.Write; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != parentKey) - { - if (parentKey.EndsWith("\\", StringComparison.Ordinal)) - { - key = String.Concat(parentKey, key); - } - else - { - key = String.Concat(parentKey, "\\", key); - } - } - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - if (root.HasValue) - { - this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); - } - - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "binary": - valueType = RegistryValueType.Binary; - break; - case "expandable": - valueType = RegistryValueType.Expandable; - break; - case "integer": - valueType = RegistryValueType.Integer; - break; - case "multiString": - valueType = RegistryValueType.MultiString; - break; - case "string": - valueType = RegistryValueType.String; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MultiString": - case "MultiStringValue": - if (RegistryValueType.MultiString != valueType && null != value) - { - this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); - } - else - { - value = this.ParseRegistryMultiStringElement(child, value); - } - break; - case "Permission": - this.ParsePermissionElement(child, id.Id, "Registry"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "Registry"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - //switch (typeType) - //{ - //case Wix.RegistryValue.TypeType.binary: - // value = String.Concat("#x", value); - // break; - //case Wix.RegistryValue.TypeType.expandable: - // value = String.Concat("#%", value); - // break; - //case Wix.RegistryValue.TypeType.integer: - // value = String.Concat("#", value); - // break; - //case Wix.RegistryValue.TypeType.multiString: - // switch (actionType) - // { - // case Wix.RegistryValue.ActionType.append: - // value = String.Concat("[~]", value); - // break; - // case Wix.RegistryValue.ActionType.prepend: - // value = String.Concat(value, "[~]"); - // break; - // case Wix.RegistryValue.ActionType.write: - // default: - // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) - // { - // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); - // } - // break; - // } - // break; - //case Wix.RegistryValue.TypeType.@string: - // // escape the leading '#' character for string registry keys - // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - // { - // value = String.Concat("#", value); - // } - // break; - //} - - // value may be set by child MultiStringValue elements, so it must be checked here - if (null == value && valueType != RegistryValueType.Binary) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values - { - this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Value = value, - ValueType = valueType, - ValueAction = actionType, - ComponentRef = componentId, - }); - } - - // If this was just a regular registry key (that could be the key path) - // and no child registry key set the possible key path, let's make this - // Registry/@Id a possible key path. - if (null == possibleKeyPath) - { - possibleKeyPath = id.Id; - } - - return keyPath; - } - - private string ParseRegistryMultiStringElement(XElement node, string value) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string multiStringValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - this.Core.ParseForExtensionElements(node); - - return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); - } - - /// - /// Parses a RemoveRegistryKey element. - /// - /// The element to parse. - /// The component identifier of the parent element. - private void ParseRemoveRegistryKeyElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - RemoveRegistryActionType? actionType = null; - string key = null; - var name = "-"; - RegistryRootType? root = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "removeOnInstall": - actionType = RemoveRegistryActionType.RemoveOnInstall; - break; - case "removeOnUninstall": - actionType = RemoveRegistryActionType.RemoveOnUninstall; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); - break; - } - //if (0 < action.Length) - //{ - // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) - // { - // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); - // } - //} - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!actionType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Action = actionType.Value, - ComponentRef = componentId, - }); - } - } - - /// - /// Parses a RemoveRegistryValue element. - /// - /// The element to parse. - /// The component identifier of the parent element. - private void ParseRemoveRegistryValueElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string name = null; - RegistryRootType? root = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - ComponentRef = componentId - }); - } - } - - /// - /// Parses a remove file element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of the parent component's directory. - private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directoryId = null; - string subdirectory = null; - string name = null; - bool? onInstall = null; - bool? onUninstall = null; - string propertyId = null; - string shortName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); - break; - case "On": - var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (onValue) - { - case "install": - onInstall = true; - break; - case "uninstall": - onUninstall = true; - break; - case "both": - onInstall = true; - onUninstall = true; - break; - } - break; - case "Property": - propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (!onInstall.HasValue && !onUninstall.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); - } - - if (String.IsNullOrEmpty(propertyId)) - { - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); - } - else if (!String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); - } - else if (!String.IsNullOrEmpty(subdirectory)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); - } - - if (null == id) - { - var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FileName = name, - ShortFileName = shortName, - DirPropertyRef = directoryId ?? propertyId ?? parentDirectory, - OnInstall = onInstall, - OnUninstall = onUninstall, - }); - } - } - - /// - /// Parses a RemoveFolder element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of parent component's directory. - private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directoryId = null; - string subdirectory = null; - bool? onInstall = null; - bool? onUninstall = null; - string propertyId = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "On": - var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (onValue) - { - case "install": - onInstall = true; - break; - case "uninstall": - onUninstall = true; - break; - case "both": - onInstall = true; - onUninstall = true; - break; - } - break; - case "Property": - propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!onInstall.HasValue && !onUninstall.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); - } - - if (String.IsNullOrEmpty(propertyId)) - { - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); - } - else if (!String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); - } - else if (!String.IsNullOrEmpty(subdirectory)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); - } - - if (null == id) - { - var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString()); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - DirPropertyRef = directoryId ?? propertyId, - OnInstall = onInstall, - OnUninstall = onUninstall - }); - } - } - - /// - /// Parses a reserve cost element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional and default identifier of referenced directory. - private void ParseReserveCostElement(XElement node, string componentId, string directoryId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string subdirectory = null; - var runFromSource = CompilerConstants.IntegerNotSet; - var runLocal = CompilerConstants.IntegerNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "RunFromSource": - runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "RunLocal": - runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - if (null == id) - { - id = this.Core.CreateIdentifier("rc", componentId, directoryId); - } - - if (CompilerConstants.IntegerNotSet == runFromSource) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); - } - - if (CompilerConstants.IntegerNotSet == runLocal) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - ReserveFolder = directoryId, - ReserveLocal = runLocal, - ReserveSource = runFromSource - }); - } - } - - /// - /// Parses a sequence element. - /// - /// Element to parse. - /// Name of sequence table. - private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) - { - // Parse each action in the sequence. - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - var actionName = child.Name.LocalName; - string afterAction = null; - string beforeAction = null; - string condition = null; - var customAction = "Custom" == actionName; - var overridable = false; - var exitSequence = CompilerConstants.IntegerNotSet; - var sequence = CompilerConstants.IntegerNotSet; - var showDialog = "Show" == actionName; - var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; - var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; - var suppress = false; - - foreach (var attrib in child.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - if (customAction) - { - actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "After": - if (customAction || showDialog || specialAction || specialStandardAction) - { - afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Before": - if (customAction || showDialog || specialAction || specialStandardAction) - { - beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Condition": - condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Dialog": - if (showDialog) - { - actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "OnExit": - if (customAction || showDialog || specialAction) - { - var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - switch (exitValue) - { - case "success": - exitSequence = -1; - break; - case "cancel": - exitSequence = -2; - break; - case "error": - exitSequence = -3; - break; - case "suspend": - exitSequence = -4; - break; - } - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Overridable": - overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); - break; - case "Sequence": - sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Suppress": - suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (customAction && "Custom" == actionName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); - } - else if (showDialog && "Show" == actionName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); - } - - if (CompilerConstants.IntegerNotSet != sequence) - { - if (CompilerConstants.IntegerNotSet != exitSequence) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); - } - else if (null != beforeAction || null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); - } - } - else // sequence not specified use OnExit (which may also be not set). - { - sequence = exitSequence; - } - - if (null != beforeAction && null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); - } - else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) - { - this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); - } - - // action that is scheduled to occur before/after itself - if (beforeAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); - } - else if (afterAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); - } - - // normal standard actions cannot be set overridable by the user (since they are overridable by default) - if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) - { - this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); - } - - // suppress cannot be specified at the same time as Before, After, or Sequence - if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); - } - - this.Core.ParseForExtensionElements(child); - - // add the row and any references needed - if (!this.Core.EncounteredError) - { - if (suppress) - { - this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) - { - SequenceTable = sequenceTable, - Action = actionName - }); - } - else - { - var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) - { - SequenceTable = sequenceTable, - Action = actionName, - Condition = condition, - Before = beforeAction, - After = afterAction, - Overridable = overridable, - }); - - if (CompilerConstants.IntegerNotSet != sequence) - { - symbol.Sequence = sequence; - } - } - } - } - - this.Core.InnerTextDisallowed(node); - } - - - /// - /// Parses a service config element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional element containing parent's service name. - private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string delayedAutoStart = null; - string failureActionsWhen = null; - var name = serviceName; - var install = false; - var reinstall = false; - var uninstall = false; - string preShutdownDelay = null; - string requiredPrivileges = null; - string sid = null; - - this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DelayedAutoStart": - delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (delayedAutoStart) - { - case "no": - delayedAutoStart = "0"; - break; - case "yes": - delayedAutoStart = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "FailureActionsWhen": - failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (failureActionsWhen) - { - case "failedToStop": - failureActionsWhen = "0"; - break; - case "failedToStopOrReturnedError": - failureActionsWhen = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "OnInstall": - install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == install) - //{ - // events |= MsiInterop.MsidbServiceConfigEventInstall; - //} - break; - case "OnReinstall": - reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == reinstall) - //{ - // events |= MsiInterop.MsidbServiceConfigEventReinstall; - //} - break; - case "OnUninstall": - uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == uninstall) - //{ - // events |= MsiInterop.MsidbServiceConfigEventUninstall; - //} - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - case "PreShutdownDelay": - preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "ServiceName": - if (!String.IsNullOrEmpty(serviceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); - } - - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ServiceSid": - sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sid) - { - case "none": - sid = "0"; - break; - case "restricted": - sid = "3"; - break; - case "unrestricted": - sid = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Get the ServiceConfig required privilegs. - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "RequiredPrivilege": - requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (!install && !reinstall && !uninstall) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); - } - - if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); - } - - if (!this.Core.EncounteredError) - { - if (!String.IsNullOrEmpty(delayedAutoStart)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.DelayedAutoStart, - Argument = delayedAutoStart, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(failureActionsWhen)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.FailureActionsFlag, - Argument = failureActionsWhen, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(sid)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.ServiceSidInfo, - Argument = sid, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(requiredPrivileges)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, - Argument = requiredPrivileges, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(preShutdownDelay)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.PreshutdownInfo, - Argument = preShutdownDelay, - ComponentRef = componentId, - }); - } - } - } - - private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string privilege = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (privilege) - { - case "assignPrimaryToken": - privilege = "SeAssignPrimaryTokenPrivilege"; - break; - case "audit": - privilege = "SeAuditPrivilege"; - break; - case "backup": - privilege = "SeBackupPrivilege"; - break; - case "changeNotify": - privilege = "SeChangeNotifyPrivilege"; - break; - case "createGlobal": - privilege = "SeCreateGlobalPrivilege"; - break; - case "createPagefile": - privilege = "SeCreatePagefilePrivilege"; - break; - case "createPermanent": - privilege = "SeCreatePermanentPrivilege"; - break; - case "createSymbolicLink": - privilege = "SeCreateSymbolicLinkPrivilege"; - break; - case "createToken": - privilege = "SeCreateTokenPrivilege"; - break; - case "debug": - privilege = "SeDebugPrivilege"; - break; - case "enableDelegation": - privilege = "SeEnableDelegationPrivilege"; - break; - case "impersonate": - privilege = "SeImpersonatePrivilege"; - break; - case "increaseBasePriority": - privilege = "SeIncreaseBasePriorityPrivilege"; - break; - case "increaseQuota": - privilege = "SeIncreaseQuotaPrivilege"; - break; - case "increaseWorkingSet": - privilege = "SeIncreaseWorkingSetPrivilege"; - break; - case "loadDriver": - privilege = "SeLoadDriverPrivilege"; - break; - case "lockMemory": - privilege = "SeLockMemoryPrivilege"; - break; - case "machineAccount": - privilege = "SeMachineAccountPrivilege"; - break; - case "manageVolume": - privilege = "SeManageVolumePrivilege"; - break; - case "profileSingleProcess": - privilege = "SeProfileSingleProcessPrivilege"; - break; - case "relabel": - privilege = "SeRelabelPrivilege"; - break; - case "remoteShutdown": - privilege = "SeRemoteShutdownPrivilege"; - break; - case "restore": - privilege = "SeRestorePrivilege"; - break; - case "security": - privilege = "SeSecurityPrivilege"; - break; - case "shutdown": - privilege = "SeShutdownPrivilege"; - break; - case "syncAgent": - privilege = "SeSyncAgentPrivilege"; - break; - case "systemEnvironment": - privilege = "SeSystemEnvironmentPrivilege"; - break; - case "systemProfile": - privilege = "SeSystemProfilePrivilege"; - break; - case "systemTime": - case "modifySystemTime": - privilege = "SeSystemtimePrivilege"; - break; - case "takeOwnership": - privilege = "SeTakeOwnershipPrivilege"; - break; - case "tcb": - case "trustedComputerBase": - privilege = "SeTcbPrivilege"; - break; - case "timeZone": - case "modifyTimeZone": - privilege = "SeTimeZonePrivilege"; - break; - case "trustedCredManAccess": - case "trustedCredentialManagerAccess": - privilege = "SeTrustedCredManAccessPrivilege"; - break; - case "undock": - privilege = "SeUndockPrivilege"; - break; - case "unsolicitedInput": - privilege = "SeUnsolicitedInputPrivilege"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (privilege == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - this.Core.ParseForExtensionElements(node); - - return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); - } - - /// - /// Parses a service config failure actions element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional element containing parent's service name. - private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var name = serviceName; - var install = false; - var reinstall = false; - var uninstall = false; - int? resetPeriod = null; - string rebootMessage = null; - string command = null; - string actions = null; - string actionsDelays = null; - - this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Command": - command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "OnInstall": - install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnReinstall": - reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnUninstall": - uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RebootMessage": - rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "ResetPeriod": - resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "ServiceName": - if (!String.IsNullOrEmpty(serviceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); - } - - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Get the ServiceConfigFailureActions actions. - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Failure": - string action = null; - string delay = null; - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - foreach (var childAttrib in child.Attributes()) - { - if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) - { - switch (childAttrib.Name.LocalName) - { - case "Action": - action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (action) - { - case "none": - action = "0"; - break; - case "restartComputer": - action = "2"; - break; - case "restartService": - action = "1"; - break; - case "runCommand": - action = "3"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "Delay": - delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - default: - this.Core.UnexpectedAttribute(child, childAttrib); - break; - } - } - } - - if (String.IsNullOrEmpty(action)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); - } - - if (String.IsNullOrEmpty(delay)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); - } - - if (!String.IsNullOrEmpty(actions)) - { - actions = String.Concat(actions, "[~]"); - } - actions = String.Concat(actions, action); - - if (!String.IsNullOrEmpty(actionsDelays)) - { - actionsDelays = String.Concat(actionsDelays, "[~]"); - } - actionsDelays = String.Concat(actionsDelays, delay); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (!install && !reinstall && !uninstall) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ResetPeriod = resetPeriod, - RebootMessage = rebootMessage, - Command = command, - Actions = actions, - DelayActions = actionsDelays, - ComponentRef = componentId, - }); - } - } - - /// - /// Parses a service control element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseServiceControlElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string arguments = null; - Identifier id = null; - string name = null; - var installRemove = false; - var uninstallRemove = false; - var installStart = false; - var uninstallStart = false; - var installStop = false; - var uninstallStop = false; - bool? wait = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Remove": - var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (removeValue) - { - case "install": - installRemove = true; - break; - case "uninstall": - uninstallRemove = true; - break; - case "both": - installRemove = true; - uninstallRemove = true; - break; - case "": - break; - } - break; - case "Start": - var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (startValue) - { - case "install": - installStart = true; - break; - case "uninstall": - uninstallStart = true; - break; - case "both": - installStart = true; - uninstallStart = true; - break; - case "": - break; - } - break; - case "Stop": - var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (stopValue) - { - case "install": - installStop = true; - break; - case "uninstall": - uninstallStop = true; - break; - case "both": - installStop = true; - uninstallStop = true; - break; - case "": - break; - } - break; - case "Wait": - wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - // get the ServiceControl arguments - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ServiceArgument": - arguments = this.ParseServiceArgument(child, arguments); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) - { - Name = name, - InstallRemove = installRemove, - UninstallRemove = uninstallRemove, - InstallStart = installStart, - UninstallStart = uninstallStart, - InstallStop = installStop, - UninstallStop = uninstallStop, - Arguments = arguments, - Wait = wait, - ComponentRef = componentId - }); - } - } - - private string ParseServiceArgument(XElement node, string arguments) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string argument = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (argument == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); - } - - /// - /// Parses a service dependency element. - /// - /// Element to parse. - /// Parsed sevice dependency name. - private string ParseServiceDependencyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string dependency = null; - var group = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Group": - group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return group ? String.Concat("+", dependency) : dependency; - } - - /// - /// Parses a service install element. - /// - /// Element to parse. - /// Identifier of parent component. - /// - private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string account = null; - string arguments = null; - string dependencies = null; - string description = null; - string displayName = null; - var eraseDescription = false; - string loadOrderGroup = null; - string name = null; - string password = null; - - var serviceType = ServiceType.OwnProcess; - var startType = ServiceStartType.Demand; - var errorControl = ServiceErrorControl.Normal; - var interactive = false; - var vital = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Account": - account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Arguments": - arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EraseDescription": - eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ErrorControl": - var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (errorControlValue) - { - case "ignore": - errorControl = ServiceErrorControl.Ignore; - break; - case "normal": - errorControl = ServiceErrorControl.Normal; - break; - case "critical": - errorControl = ServiceErrorControl.Critical; - break; - case "": // error case handled by GetAttributeValue() - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); - break; - } - break; - case "Interactive": - interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LoadOrderGroup": - loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Password": - password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Start": - var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (startValue) - { - case "auto": - startType = ServiceStartType.Auto; - break; - case "demand": - startType = ServiceStartType.Demand; - break; - case "disabled": - startType = ServiceStartType.Disabled; - break; - case "boot": - case "system": - this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); - break; - } - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "ownProcess": - serviceType = ServiceType.OwnProcess; - break; - case "shareProcess": - serviceType = ServiceType.ShareProcess; - break; - case "kernelDriver": - case "systemDriver": - this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); - break; - } - break; - case "Vital": - vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (0 == startType) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); - } - - if (eraseDescription) - { - description = "[~]"; - } - - // get the ServiceInstall dependencies and config - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); - break; - case "ServiceConfig": - this.ParseServiceConfigElement(child, componentId, name); - break; - case "ServiceConfigFailureActions": - this.ParseServiceConfigFailureActionsElement(child, componentId, name); - break; - case "ServiceDependency": - dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (null != dependencies) - { - dependencies = String.Concat(dependencies, "[~]"); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) - { - Name = name, - DisplayName = displayName, - ServiceType = serviceType, - StartType = startType, - ErrorControl = errorControl, - LoadOrderGroup = loadOrderGroup, - Dependencies = dependencies, - StartName = account, - Password = password, - Arguments = arguments, - ComponentRef = componentId, - Description = description, - Interactive = interactive, - Vital = vital - }); - } - } - - /// - /// Parses a SetDirectory element. - /// - /// Element to parse. - private void ParseSetDirectoryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string actionName = null; - string id = null; - string condition = null; - var executionType = CustomActionExecutionType.Immediate; - var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); - break; - case "Sequence": - var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sequenceValue) - { - case "execute": - sequences = new[] { SequenceTable.InstallExecuteSequence }; - break; - case "first": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "ui": - sequences = new[] { SequenceTable.InstallUISequence }; - break; - case "both": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (String.IsNullOrEmpty(actionName)) - { - actionName = String.Concat("Set", id); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) - { - ExecutionType = executionType, - SourceType = CustomActionSourceType.Directory, - TargetType = CustomActionTargetType.TextData, - Source = id, - Target = value - }); - - foreach (var sequence in sequences) - { - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, afterAction: "CostInitialize"); - } - } - } - - /// - /// Parses a SetProperty element. - /// - /// Element to parse. - private void ParseSetPropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string actionName = null; - string id = null; - string condition = null; - string afterAction = null; - string beforeAction = null; - var executionType = CustomActionExecutionType.Immediate; - var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Before": - beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Sequence": - var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sequenceValue) - { - case "execute": - sequences = new[] { SequenceTable.InstallExecuteSequence }; - break; - case "first": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "ui": - sequences = new[] { SequenceTable.InstallUISequence }; - break; - case "both": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (String.IsNullOrEmpty(actionName)) - { - actionName = String.Concat("Set", id); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - if (null != beforeAction && null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); - } - else if (null == beforeAction && null == afterAction) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); - } - - this.Core.ParseForExtensionElements(node); - - // add the row and any references needed - if (!this.Core.EncounteredError) - { - // action that is scheduled to occur before/after itself - if (beforeAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); - } - else if (afterAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); - } - - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) - { - ExecutionType = executionType, - SourceType = CustomActionSourceType.Property, - TargetType = CustomActionTargetType.TextData, - Source = id, - Target = value, - }); - - foreach (var sequence in sequences) - { - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, beforeAction, afterAction); - } - } - } - - /// - /// Parses a SFP catalog element. - /// - /// Element to parse. - /// Parent SFPCatalog. - private void ParseSFPFileElement(XElement node, string parentSFPCatalog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) - { - FileRef = id, - SFPCatalogRef = parentSFPCatalog - }); - } - } - - /// - /// Parses a SFP catalog element. - /// - /// Element to parse. - /// Parent SFPCatalog. - private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string parentName = null; - string dependency = null; - string name = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Dependency": - dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - parentSFPCatalog = name; - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SFPCatalog": - this.ParseSFPCatalogElement(child, ref parentName); - if (null != dependency && parentName == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); - } - dependency = parentName; - break; - case "SFPFile": - this.ParseSFPFileElement(child, name); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) - { - SFPCatalog = name, - Catalog = sourceFile, - Dependency = dependency - }); - } - } - - /// - /// Parses a shortcut element. - /// - /// Element to parse. - /// Identifer for parent component. - /// Local name of parent element. - /// Default identifier of parent (which is usually the target). - /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). - private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var advertise = false; - string arguments = null; - string description = null; - string descriptionResourceDll = null; - int? descriptionResourceId = null; - string directoryId = null; - string subdirectory = null; - string displayResourceDll = null; - int? displayResourceId = null; - int? hotkey = null; - string icon = null; - int? iconIndex = null; - string name = null; - string shortName = null; - ShortcutShowType? show = null; - string target = null; - string workingDirectoryId = null; - string workingSubdirectory = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Advertise": - advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Arguments": - arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DescriptionResourceDll": - descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DescriptionResourceId": - descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "DisplayResourceDll": - displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayResourceId": - displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Hotkey": - hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Show": - var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (showValue) - { - case "normal": - show = ShortcutShowType.Normal; - break; - case "maximized": - show = ShortcutShowType.Maximized; - break; - case "minimized": - show = ShortcutShowType.Minimized; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); - break; - } - break; - case "Target": - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "WorkingDirectory": - workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId); - break; - case "WorkingSubdirectory": - workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (advertise && null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); - } - - if (null == directoryId) - { - if ("Component" == parentElementLocalName) - { - directoryId = defaultTarget; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); - } - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - if (null != descriptionResourceDll) - { - if (!descriptionResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); - } - } - else - { - if (descriptionResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); - } - } - - if (null != displayResourceDll) - { - if (!displayResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); - } - } - else - { - if (displayResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory"); - - if ("Component" != parentElementLocalName && null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Icon": - icon = this.ParseIconElement(child); - break; - case "ShortcutProperty": - this.ParseShortcutPropertyElement(child, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (advertise) - { - if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) - { - this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); - } - - target = Guid.Empty.ToString("B"); - } - else if (null != target) - { - } - else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) - { - target = "[" + defaultTarget + "]"; - } - else if ("File" == parentElementLocalName) - { - target = "[#" + defaultTarget + "]"; - } - - this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) - { - DirectoryRef = directoryId, - Name = name, - ShortName = shortName, - ComponentRef = componentId, - Target = target, - Arguments = arguments, - Description = description, - Hotkey = hotkey, - IconRef = icon, - IconIndex = iconIndex, - Show = show, - WorkingDirectory = workingDirectoryId, - DisplayResourceDll = displayResourceDll, - DisplayResourceId = displayResourceId, - DescriptionResourceDll = descriptionResourceDll, - DescriptionResourceId = descriptionResourceId, - }); - } - } - - /// - /// Parses a shortcut property element. - /// - /// Element to parse. - /// - private void ParseShortcutPropertyElement(XElement node, string shortcutId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(key)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - else if (null == id) - { - id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); - } - - if (String.IsNullOrEmpty(value)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) - { - ShortcutRef = shortcutId, - PropertyKey = key, - PropVariantValue = value - }); - } - } - - /// - /// Parses a typelib element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of file that acts as typelib server. - /// true if the component is 64-bit. - private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var advertise = YesNoType.NotSet; - var cost = CompilerConstants.IntegerNotSet; - string description = null; - var flags = 0; - string helpDirectoryId = null; - string helpSubdirectory = null; - var language = CompilerConstants.IntegerNotSet; - var majorVersion = CompilerConstants.IntegerNotSet; - var minorVersion = CompilerConstants.IntegerNotSet; - var resourceId = CompilerConstants.LongNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Advertise": - advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Control": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 2; - } - break; - case "Cost": - cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HasDiskImage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 8; - } - break; - case "HelpDirectory": - helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId); - break; - case "HelpSubdirectory": - helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 4; - } - break; - case "Language": - language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "MajorVersion": - majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); - break; - case "MinorVersion": - minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - break; - case "ResourceId": - resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); - break; - case "Restricted": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (CompilerConstants.IntegerNotSet == language) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - language = CompilerConstants.IllegalInteger; - } - - helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory"); - - // build up the typelib version string for the registry if the major or minor version was specified - string registryVersion = null; - if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) - { - if (CompilerConstants.IntegerNotSet != majorVersion) - { - registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); - } - else - { - registryVersion = "0"; - } - - if (CompilerConstants.IntegerNotSet != minorVersion) - { - registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); - } - else - { - registryVersion = String.Concat(registryVersion, ".0"); - } - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); - break; - case "Class": - this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); - break; - case "Interface": - this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (YesNoType.Yes == advertise) - { - if (CompilerConstants.LongNotSet != resourceId) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); - } - - if (0 != flags) - { - if (0x1 == (flags & 0x1)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); - } - - if (0x2 == (flags & 0x2)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); - } - - if (0x4 == (flags & 0x4)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); - } - - if (0x8 == (flags & 0x8)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) - { - LibId = id, - Language = language, - ComponentRef = componentId, - Description = description, - DirectoryRef = helpDirectoryId, - FeatureRef = Guid.Empty.ToString("B") - }); - - if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) - { - symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); - } - - if (CompilerConstants.IntegerNotSet != cost) - { - symbol.Cost = cost; - } - } - } - else if (YesNoType.No == advertise) - { - if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); - } - - if (null == fileServer) - { - this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); - } - - if (null == registryVersion) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); - } - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] - var path = String.Concat("[#", fileServer, "]"); - if (CompilerConstants.LongNotSet != resourceId) - { - path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); - - if (null != helpDirectoryId) - { - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId); - } - } - } - - /// - /// Parses an upgrade element. - /// - /// Element to parse. - private void ParseUpgradeElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - // process the UpgradeVersion children here - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - switch (child.Name.LocalName) - { - case "Property": - this.ParsePropertyElement(child); - this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); - break; - case "UpgradeVersion": - this.ParseUpgradeVersionElement(child, id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // No rows created here. All row creation is done in ParseUpgradeVersionElement. - } - - /// - /// Parse upgrade version element. - /// - /// Element to parse. - /// Upgrade code. - private void ParseUpgradeVersionElement(XElement node, string upgradeId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string actionProperty = null; - string language = null; - string maximum = null; - string minimum = null; - var excludeLanguages = false; - var ignoreFailures = false; - var includeMax = false; - var includeMin = true; - var migrateFeatures = false; - var onlyDetect = false; - string removeFeatures = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludeLanguages": - excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IgnoreRemoveFailure": - ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMaximum": - includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": // this is "yes" by default - includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Language": - language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "MigrateFeatures": - migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnlyDetect": - onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Property": - actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "RemoveFeatures": - removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == actionProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) - { - this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); - } - - if (null == minimum && null == maximum) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeId, - VersionMin = minimum, - VersionMax = maximum, - Language = language, - ExcludeLanguages = excludeLanguages, - IgnoreRemoveFailures = ignoreFailures, - VersionMaxInclusive = includeMax, - VersionMinInclusive = includeMin, - MigrateFeatures = migrateFeatures, - OnlyDetect = onlyDetect, - Remove = removeFeatures, - ActionProperty = actionProperty - }); - - // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence - // if at least one row in Upgrade table lacks the OnlyDetect attribute. - if (!onlyDetect) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); - } - } - } - - /// - /// Parses a verb element. - /// - /// Element to parse. - /// Extension verb is releated to. - /// Optional progId for extension. - /// Identifier for parent component. - /// Flag if verb is advertised. - private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string argument = null; - string command = null; - var sequence = CompilerConstants.IntegerNotSet; - string targetFile = null; - string targetProperty = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Argument": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Command": - command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Sequence": - sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "TargetFile": - targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); - break; - case "TargetProperty": - targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null != targetFile && null != targetProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); - } - - this.Core.ParseForExtensionElements(node); - - if (YesNoType.Yes == advertise) - { - if (null != targetFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); - } - - if (null != targetProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) - { - ExtensionRef = extension, - Verb = id, - Command = command, - Argument = argument, - }); - - if (CompilerConstants.IntegerNotSet != sequence) - { - symbol.Sequence = sequence; - } - } - } - else if (YesNoType.No == advertise) - { - if (CompilerConstants.IntegerNotSet != sequence) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); - } - - if (null == targetFile && null == targetProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); - } - - string target = null; - if (null != targetFile) - { - target = String.Concat("\"[#", targetFile, "]\""); - } - else if (null != targetProperty) - { - target = String.Concat("\"[", targetProperty, "]\""); - } - - if (null != argument) - { - target = String.Concat(target, " ", argument); - } - - var prefix = progId ?? String.Concat(".", extension); - - if (null != command) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); - } - } - - /// - /// Parses a WixVariable element. - /// - /// Element to parse. - private void ParseWixVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var overridable = false; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Overridable": - overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) - { - Value = value, - Overridable = overridable - }); - } - } - - private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); - switch (compressionLevel) - { - case "high": - return CompressionLevel.High; - case "low": - return CompressionLevel.Low; - case "medium": - return CompressionLevel.Medium; - case "mszip": - return CompressionLevel.Mszip; - case "none": - return CompressionLevel.None; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); - break; - } - - return null; - } - } -} diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs deleted file mode 100644 index c9cae183..00000000 --- a/src/WixToolset.Core/Compiler_Patch.cs +++ /dev/null @@ -1,657 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses an patch element. - /// - /// The element to parse. - private void ParsePatchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string patchId = null; - string codepage = null; - ////bool versionMismatches = false; - ////bool productMismatches = false; - var allowRemoval = false; - string classification = null; - string clientPatchId = null; - string description = null; - string displayName = null; - string comments = null; - string manufacturer = null; - var minorUpdateTargetRTM = YesNoType.NotSet; - string moreInfoUrl = null; - var optimizeCA = CompilerConstants.IntegerNotSet; - var optimizedInstallMode = YesNoType.NotSet; - string targetProductName = null; - // string replaceGuids = String.Empty; - var apiPatchingSymbolFlags = 0; - var optimizePatchSizeForLargeFiles = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - case "AllowMajorVersionMismatches": - ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "AllowProductCodeMismatches": - ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "AllowRemoval": - allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "Classification": - classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ClientPatchId": - clientPatchId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Comments": - comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MinorUpdateTargetRTM": - minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "MoreInfoURL": - moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "OptimizedInstallMode": - optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TargetProductName": - targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ApiPatchingSymbolNoImagehlpFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoImagehlp : 0; - break; - case "ApiPatchingSymbolNoFailuresFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoFailures : 0; - break; - case "ApiPatchingSymbolUndecoratedTooFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolUndecoratedToo : 0; - break; - case "OptimizePatchSizeForLargeFiles": - optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (patchId == null || patchId == "*") - { - // auto-generate at compile time, since this value gets dispersed to several locations - patchId = Common.GenerateGuid(); - } - this.activeName = patchId; - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - if (null == classification) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); - } - if (null == clientPatchId) - { - clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); - } - if (null == description) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); - } - if (null == displayName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); - } - if (null == manufacturer) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); - } - - this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PatchInformation": - this.ParsePatchInformationElement(child); - break; - case "Media": - this.ParseMediaElement(child, patchId); - break; - case "OptimizeCustomActions": - optimizeCA = this.ParseOptimizeCustomActionsElement(child); - break; - case "PatchFamily": - this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchFamilyRef": - this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchFamilyGroup": - this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchFamilyGroupRef": - this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchProperty": - this.ParsePatchPropertyElement(child, true); - break; - case "TargetProductCodes": - this.ParseTargetProductCodesElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) - { - Codepage = codepage, - ClientPatchId = clientPatchId, - OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, - ApiPatchingSymbolFlags = apiPatchingSymbolFlags, - }); - - if (allowRemoval) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0"); - } - - if (null != classification) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification); - } - - // always generate the CreationTimeUTC - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture)); - } - - if (null != description) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description); - } - - if (null != displayName) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); - } - - if (null != manufacturer) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer); - } - - if (YesNoType.NotSet != minorUpdateTargetRTM) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"); - } - - if (null != moreInfoUrl) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); - } - - if (CompilerConstants.IntegerNotSet != optimizeCA) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); - } - - if (YesNoType.NotSet != optimizedInstallMode) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); - } - - if (null != targetProductName) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); - } - - if (null != comments) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments); - } - } - // TODO: do something with versionMismatches and productMismatches - } - - /// - /// Parses the OptimizeCustomActions element. - /// - /// Element to parse. - /// The combined integer value for callers to store as appropriate. - private int ParseOptimizeCustomActionsElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var optimizeCA = OptimizeCAFlags.None; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "SkipAssignment": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - optimizeCA |= OptimizeCAFlags.SkipAssignment; - } - break; - case "SkipImmediate": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - optimizeCA |= OptimizeCAFlags.SkipImmediate; - } - break; - case "SkipDeferred": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - optimizeCA |= OptimizeCAFlags.SkipDeferred; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - return (int)optimizeCA; - } - - /// - /// Parses a PatchFamily element. - /// - /// The element to parse. - /// - /// - private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string productCode = null; - string version = null; - var attributes = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Supersede": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= 0x1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - if (String.IsNullOrEmpty(version)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidProductVersion(version)) - { - this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); - } - - // find unexpected child elements - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "All": - this.ParseAllElement(child); - break; - case "BinaryRef": - this.ParsePatchChildRefElement(child, "Binary"); - break; - case "ComponentRef": - this.ParsePatchChildRefElement(child, "Component"); - break; - case "CustomActionRef": - this.ParsePatchChildRefElement(child, "CustomAction"); - break; - case "DirectoryRef": - this.ParsePatchChildRefElement(child, "Directory"); - break; - case "DigitalCertificateRef": - this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); - break; - case "FeatureRef": - this.ParsePatchChildRefElement(child, "Feature"); - break; - case "IconRef": - this.ParsePatchChildRefElement(child, "Icon"); - break; - case "PropertyRef": - this.ParsePatchChildRefElement(child, "Property"); - break; - case "SoftwareTagRef": - this.ParseTagRefElement(child); - break; - case "UIRef": - this.ParsePatchChildRefElement(child, "WixUI"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiPatchSequenceSymbol(sourceLineNumbers) - { - PatchFamily = id.Id, - ProductCode = productCode, - Sequence = version, - Attributes = attributes - }); - - if (ComplexReferenceParentType.Unknown != parentType) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); - } - } - } - - /// - /// Parses a PatchFamilyGroup element. - /// - /// Element to parse. - /// - /// - private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PatchFamily": - this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); - break; - case "PatchFamilyRef": - this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); - break; - case "PatchFamilyGroupRef": - this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchFamilyGroupSymbol(sourceLineNumbers, id)); - - //Add this PatchFamilyGroup and its parent in WixGroup. - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id); - } - } - - /// - /// Parses a PatchFamilyGroup reference element. - /// - /// Element to parse. - /// The type of parent. - /// Identifier of parent element. - private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixPatchFamilyGroup, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true); - } - } - - /// - /// Parses a TargetProductCodes element. - /// - /// The element to parse. - private void ParseTargetProductCodesElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var replace = false; - var targetProductCodes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Replace": - replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "TargetProductCode": - var id = this.ParseTargetProductCodeElement(child); - if (0 == String.CompareOrdinal("*", id)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); - } - else - { - targetProductCodes.Add(id); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - // By default, target ProductCodes should be added. - if (!replace) - { - this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) - { - ProductCode = "*" - }); - } - - foreach (var targetProductCode in targetProductCodes) - { - this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) - { - ProductCode = targetProductCode - }); - } - } - } - - private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) - { - this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) - { - Company = company, - Property = property, - Value = value - }); - } - } -} diff --git a/src/WixToolset.Core/Compiler_PatchCreation.cs b/src/WixToolset.Core/Compiler_PatchCreation.cs deleted file mode 100644 index 81ae4121..00000000 --- a/src/WixToolset.Core/Compiler_PatchCreation.cs +++ /dev/null @@ -1,1265 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a patch creation element. - /// - /// The element to parse. - private void ParsePatchCreationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var clean = true; // Default is to clean - var codepage = 0; - string outputPath = null; - var productMismatches = false; - var replaceGuids = String.Empty; - string sourceList = null; - string symbolFlags = null; - var targetProducts = String.Empty; - var versionMismatches = false; - var wholeFiles = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - this.activeName = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "AllowMajorVersionMismatches": - versionMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "AllowProductCodeMismatches": - productMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "CleanWorkingFolder": - clean = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); - break; - case "OutputPath": - outputPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SourceList": - sourceList = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SymbolFlags": - symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, UInt32.MaxValue)); - break; - case "WholeFilesOnly": - wholeFiles = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Family": - this.ParseFamilyElement(child); - break; - case "PatchInformation": - this.ParsePatchInformationElement(child); - break; - case "PatchMetadata": - this.ParsePatchMetadataElement(child); - break; - case "PatchProperty": - this.ParsePatchPropertyElement(child, false); - break; - case "PatchSequence": - this.ParsePatchSequenceElement(child); - break; - case "ReplacePatch": - replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child)); - break; - case "TargetProductCode": - var targetProduct = this.ParseTargetProductCodeElement(child); - if (0 < targetProducts.Length) - { - targetProducts = String.Concat(targetProducts, ";"); - } - targetProducts = String.Concat(targetProducts, targetProduct); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - this.AddPrivateProperty(sourceLineNumbers, "PatchGUID", this.activeName); - this.AddPrivateProperty(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0"); - this.AddPrivateProperty(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0"); - this.AddPrivateProperty(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1"); - this.AddPrivateProperty(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0"); - - if (null != symbolFlags) - { - this.AddPrivateProperty(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags); - } - - if (0 < replaceGuids.Length) - { - this.AddPrivateProperty(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids); - } - - if (0 < targetProducts.Length) - { - this.AddPrivateProperty(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts); - } - - if (null != outputPath) - { - this.AddPrivateProperty(sourceLineNumbers, "PatchOutputPath", outputPath); - } - - if (null != sourceList) - { - this.AddPrivateProperty(sourceLineNumbers, "PatchSourceList", sourceList); - } - } - - /// - /// Parses a family element. - /// - /// The element to parse. - private void ParseFamilyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var diskId = CompilerConstants.IntegerNotSet; - string diskPrompt = null; - string mediaSrcProp = null; - string name = null; - var sequenceStart = CompilerConstants.IntegerNotSet; - string volumeLabel = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "DiskPrompt": - diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MediaSrcProp": - mediaSrcProp = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SequenceStart": - sequenceStart = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); - break; - case "VolumeLabel": - volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (0 < name.Length) - { - if (8 < name.Length) // check the length - { - this.Core.Write(ErrorMessages.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length)); - } - else // check for illegal characters - { - foreach (var character in name) - { - if (!Char.IsLetterOrDigit(character) && '_' != character) - { - this.Core.Write(ErrorMessages.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name)); - } - } - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "UpgradeImage": - this.ParseUpgradeImageElement(child, name); - break; - case "ExternalFile": - this.ParseExternalFileElement(child, name); - break; - case "ProtectFile": - this.ParseProtectFileElement(child, name); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ImageFamiliesSymbol(sourceLineNumbers) - { - Family = name, - MediaSrcPropName = mediaSrcProp, - DiskPrompt = diskPrompt, - VolumeLabel = volumeLabel - }); - - if (CompilerConstants.IntegerNotSet != diskId) - { - symbol.MediaDiskId = diskId; - } - - if (CompilerConstants.IntegerNotSet != sequenceStart) - { - symbol.FileSequenceStart = sequenceStart; - } - } - } - - /// - /// Parses an upgrade image element. - /// - /// The element to parse. - /// The family for this element. - private void ParseUpgradeImageElement(XElement node, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string sourceFile = null; - string sourcePatch = null; - var symbols = new List(); - string upgrade = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - upgrade = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (13 < upgrade.Length) - { - this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13)); - } - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SourcePatch": - sourcePatch = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == upgrade) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SymbolPath": - symbols.Add(this.ParseSymbolPathElement(child)); - break; - case "TargetImage": - this.ParseTargetImageElement(child, upgrade, family); - break; - case "UpgradeFile": - this.ParseUpgradeFileElement(child, upgrade); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradedImagesSymbol(sourceLineNumbers) - { - Upgraded = upgrade, - MsiPath = sourceFile, - PatchMsiPath = sourcePatch, - SymbolPaths = String.Join(";", symbols), - Family = family - }); - } - } - - /// - /// Parses an upgrade file element. - /// - /// The element to parse. - /// The upgrade key for this element. - private void ParseUpgradeFileElement(XElement node, string upgrade) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var allowIgnoreOnError = false; - string file = null; - var ignore = false; - var symbols = new List(); - var wholeFile = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AllowIgnoreOnError": - allowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "File": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Ignore": - ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "WholeFile": - wholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SymbolPath": - symbols.Add(this.ParseSymbolPathElement(child)); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (ignore) - { - this.Core.AddSymbol(new UpgradedFilesToIgnoreSymbol(sourceLineNumbers) - { - Upgraded = upgrade, - FTK = file - }); - } - else - { - this.Core.AddSymbol(new UpgradedFilesOptionalDataSymbol(sourceLineNumbers) - { - Upgraded = upgrade, - FTK = file, - SymbolPaths = String.Join(";", symbols), - AllowIgnoreOnPatchError = allowIgnoreOnError, - IncludeWholeFile = wholeFile - }); - } - } - } - - /// - /// Parses a target image element. - /// - /// The element to parse. - /// The upgrade key for this element. - /// The family key for this element. - private void ParseTargetImageElement(XElement node, string upgrade, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var ignore = false; - var order = CompilerConstants.IntegerNotSet; - string sourceFile = null; - string symbols = null; - string target = null; - string validation = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (target.Length > 13) - { - this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13)); - } - break; - case "IgnoreMissingFiles": - ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Order": - order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Validation": - validation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == target) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - if (CompilerConstants.IntegerNotSet == order) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SymbolPath": - if (null != symbols) - { - symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child)); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "TargetFile": - this.ParseTargetFileElement(child, target, family); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new TargetImagesSymbol(sourceLineNumbers) - { - Target = target, - MsiPath = sourceFile, - SymbolPaths = symbols, - Upgraded = upgrade, - Order = order, - ProductValidateFlags = validation, - IgnoreMissingSrcFiles = ignore - }); - } - } - - /// - /// Parses an upgrade file element. - /// - /// The element to parse. - /// The upgrade key for this element. - /// The family key for this element. - private void ParseTargetFileElement(XElement node, string target, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string file = null; - string ignoreLengths = null; - string ignoreOffsets = null; - string protectLengths = null; - string protectOffsets = null; - string symbols = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "SymbolPath": - symbols = this.ParseSymbolPathElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new TargetFilesOptionalDataSymbol(sourceLineNumbers) - { - Target = target, - FTK = file, - SymbolPaths = symbols, - IgnoreOffsets = ignoreOffsets, - IgnoreLengths = ignoreLengths, - }); - - if (null != protectOffsets) - { - symbol.RetainOffsets = protectOffsets; - - this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - RetainOffsets = protectOffsets, - RetainLengths = protectLengths, - }); - } - } - } - - /// - /// Parses an external file element. - /// - /// The element to parse. - /// The family for this element. - private void ParseExternalFileElement(XElement node, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string file = null; - string ignoreLengths = null; - string ignoreOffsets = null; - var order = CompilerConstants.IntegerNotSet; - string protectLengths = null; - string protectOffsets = null; - string source = null; - string symbols = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "File": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Order": - order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); - } - - if (null == source) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source")); - } - - if (CompilerConstants.IntegerNotSet == order) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "SymbolPath": - symbols = this.ParseSymbolPathElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ExternalFilesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - FilePath = source, - SymbolPaths = symbols, - IgnoreOffsets = ignoreOffsets, - IgnoreLengths = ignoreLengths, - }); - - if (null != protectOffsets) - { - symbol.RetainOffsets = protectOffsets; - } - - if (CompilerConstants.IntegerNotSet != order) - { - symbol.Order = order; - } - - if (null != protectOffsets) - { - this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - RetainOffsets = protectOffsets, - RetainLengths = protectLengths, - }); - } - } - } - - /// - /// Parses a protect file element. - /// - /// The element to parse. - /// The family for this element. - private void ParseProtectFileElement(XElement node, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string file = null; - string protectLengths = null; - string protectOffsets = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "File": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == protectOffsets || null == protectLengths) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - RetainOffsets = protectOffsets, - RetainLengths = protectLengths - }); - } - } - - /// - /// Parses a range element (ProtectRange, IgnoreRange, etc). - /// - /// The element to parse. - /// Reference to the offsets string. - /// Reference to the lengths string. - private void ParseRangeElement(XElement node, ref string offsets, ref string lengths) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string length = null; - string offset = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Length": - length = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Offset": - offset = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == length) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length")); - } - - if (null == offset) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); - } - - this.Core.ParseForExtensionElements(node); - - if (null != lengths) - { - lengths = String.Concat(lengths, ",", length); - } - else - { - lengths = length; - } - - if (null != offsets) - { - offsets = String.Concat(offsets, ",", offset); - } - else - { - offsets = offset; - } - } - - /// - /// Parses a patch metadata element. - /// - /// Element to parse. - private void ParsePatchMetadataElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var allowRemoval = YesNoType.NotSet; - string classification = null; - string creationTimeUtc = null; - string description = null; - string displayName = null; - string manufacturerName = null; - string minorUpdateTargetRTM = null; - string moreInfoUrl = null; - var optimizeCA = CompilerConstants.IntegerNotSet; - var optimizedInstallMode = YesNoType.NotSet; - string targetProductName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AllowRemoval": - allowRemoval = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Classification": - classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CreationTimeUTC": - creationTimeUtc = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ManufacturerName": - manufacturerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MinorUpdateTargetRTM": - minorUpdateTargetRTM = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MoreInfoURL": - moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "OptimizedInstallMode": - optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TargetProductName": - targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (YesNoType.NotSet == allowRemoval) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval")); - } - - if (null == classification) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); - } - - if (null == description) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); - } - - if (null == displayName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); - } - - if (null == manufacturerName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName")); - } - - if (null == moreInfoUrl) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL")); - } - - if (null == targetProductName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "CustomProperty": - this.ParseCustomPropertyElement(child); - break; - case "OptimizeCustomActions": - optimizeCA = this.ParseOptimizeCustomActionsElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (YesNoType.NotSet != allowRemoval) - { - this.AddPatchMetadata(sourceLineNumbers, null, "AllowRemoval", YesNoType.Yes == allowRemoval ? "1" : "0"); - } - - if (null != classification) - { - this.AddPatchMetadata(sourceLineNumbers, null, "Classification", classification); - } - - if (null != creationTimeUtc) - { - this.AddPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", creationTimeUtc); - } - - if (null != description) - { - this.AddPatchMetadata(sourceLineNumbers, null, "Description", description); - } - - if (null != displayName) - { - this.AddPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); - } - - if (null != manufacturerName) - { - this.AddPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturerName); - } - - if (null != minorUpdateTargetRTM) - { - this.AddPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", minorUpdateTargetRTM); - } - - if (null != moreInfoUrl) - { - this.AddPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); - } - - if (CompilerConstants.IntegerNotSet != optimizeCA) - { - this.AddPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); - } - - if (YesNoType.NotSet != optimizedInstallMode) - { - this.AddPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); - } - - if (null != targetProductName) - { - this.AddPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); - } - } - } - - /// - /// Parses a custom property element for the PatchMetadata table. - /// - /// Element to parse. - private void ParseCustomPropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string company = null; - string property = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Company": - company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Property": - property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == company) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.AddPatchMetadata(sourceLineNumbers, company, property, value); - } - } - - /// - /// Parses a patch sequence element. - /// - /// The element to parse. - private void ParsePatchSequenceElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string family = null; - string target = null; - string sequence = null; - var attributes = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "PatchFamily": - family = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ProductCode": - if (null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage")); - } - target = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Target": - if (null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode")); - } - this.Core.Write(WarningMessages.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "TargetImage": - if (null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.TargetImages, target); - break; - case "Sequence": - sequence = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Supersede": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= 0x1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == family) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new PatchSequenceSymbol(sourceLineNumbers) - { - PatchFamily = family, - Target = target, - Sequence = sequence, - Supersede = attributes, - }); - } - } - - private void AddPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) - { - this.Core.AddSymbol(new PatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) - { - Company = company, - Property = property, - Value = value, - }); - } - } -} diff --git a/src/WixToolset.Core/Compiler_Tag.cs b/src/WixToolset.Core/Compiler_Tag.cs deleted file mode 100644 index cf55c448..00000000 --- a/src/WixToolset.Core/Compiler_Tag.cs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a Tag element for Software Id Tag registration under a Bundle element. - /// - /// The element to parse. - private void ParseBundleTagElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string regid = null; - string installPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Regid": - regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "InstallDirectory": - case "Bitness": - this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); - break; - case "InstallPath": - installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(name)) - { - name = node.Parent?.Attribute("Name")?.Value; - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - } - - if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) - { - this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); - } - - if (String.IsNullOrEmpty(regid)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); - } - else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); - } - - if (String.IsNullOrEmpty(installPath)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) - { - Filename = String.Concat(name, ".swidtag"), - Regid = regid, - Name = name, - InstallPath = installPath - }); - } - } - - /// - /// Parses a Tag element for Software Id Tag registration under a Package element. - /// - /// The element to parse. - private void ParsePackageTagElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - string regid = null; - string feature = null; - string installDirectory = null; - var win64 = this.Context.IsCurrentPlatform64Bit; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Regid": - regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "InstallDirectory": - installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "InstallPath": - this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - win64 = false; - break; - case "always64": - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(name)) - { - name = node.Parent?.Attribute("Name")?.Value; - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - } - - if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) - { - this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); - } - - if (String.IsNullOrEmpty(regid)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); - } - else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); - return; - } - else if (id == null) - { - id = this.CreateTagId(regid); - } - - if (String.IsNullOrEmpty(installDirectory)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); - } - - if (!this.Core.EncounteredError) - { - var fileName = String.Concat(name, ".swidtag"); - - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); - this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) - { - Name = "swidtag", - ParentDirectoryRef = installDirectory, - ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" - }); - - this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) - { - ComponentId = "*", - DirectoryRef = id.Id, - KeyPath = id.Id, - KeyPathType = ComponentKeyPathType.File, - Location = ComponentLocation.LocalOnly, - Win64 = win64 - }); - - this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) - { - ComponentRef = id.Id, - Name = fileName, - DiskId = 1, - Attributes = FileSymbolAttributes.ReadOnly, - }); - - if (!String.IsNullOrEmpty(feature)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); - } - else - { - feature = "WixSwidTag"; - this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, feature)) - { - Title = "ISO/IEC 19770-2", - Level = 1, - InstallDefault = FeatureInstallDefault.Local, - Display = 0, - DisallowAdvertise = true, - DisallowAbsent = true, - }); - } - this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); - - this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); - this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) - { - FileRef = id.Id, - Regid = regid, - Name = name - }); - } - } - - /// - /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. - /// - /// The element to parse. - private void ParseTagRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string regid = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Regid": - regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(regid)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); - } - else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); - } - - if (!this.Core.EncounteredError) - { - var id = this.CreateTagId(regid); - - this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) - { - Table = SymbolDefinitions.Component.Name, - PrimaryKeys = id.Id - }); - } - } - - private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); - } -} diff --git a/src/WixToolset.Core/Compiler_UI.cs b/src/WixToolset.Core/Compiler_UI.cs deleted file mode 100644 index d712ec91..00000000 --- a/src/WixToolset.Core/Compiler_UI.cs +++ /dev/null @@ -1,1808 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - // NameToBit arrays - private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; - private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; - private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; - private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; - private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; - private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; - private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; - private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; - private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; - private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; - private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; - - /// - /// Parses UI elements. - /// - /// Element to parse. - private void ParseUIElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var embeddedUICount = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "BillboardAction": - this.ParseBillboardActionElement(child); - break; - case "ComboBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); - break; - case "Dialog": - this.ParseDialogElement(child); - break; - case "DialogRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Dialog); - break; - case "EmbeddedUI": - if (0 < embeddedUICount) // there can be only one embedded UI - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - this.ParseEmbeddedUIElement(child); - ++embeddedUICount; - break; - case "Error": - this.ParseErrorElement(child); - break; - case "ListBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); - break; - case "ListView": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); - break; - case "ProgressText": - this.ParseActionTextElement(child); - break; - case "Publish": - var order = 0; - this.ParsePublishElement(child, null, null, ref order); - break; - case "RadioButtonGroup": - var radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet); - if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers)); - } - break; - case "TextStyle": - this.ParseTextStyleElement(child); - break; - case "UIText": - this.ParseUITextElement(child); - break; - - // the following are available indentically under the UI and Product elements for document organization use only - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null != id && !this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixUISymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a list item element. - /// - /// Element to parse. - /// Type of symbol to create. - /// Identifier of property referred to by list item. - /// Relative order of list items. - private void ParseListItemElement(XElement node, SymbolDefinitionType symbolType, string property, ref int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string icon = null; - string text = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Icon": - if (SymbolDefinitionType.ListView == symbolType) - { - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, icon); - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView")); - } - break; - case "Text": - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - switch (symbolType) - { - case SymbolDefinitionType.ComboBox: - this.Core.AddSymbol(new ComboBoxSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - }); - break; - case SymbolDefinitionType.ListBox: - this.Core.AddSymbol(new ListBoxSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - }); - break; - case SymbolDefinitionType.ListView: - var symbol = this.Core.AddSymbol(new ListViewSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - }); - - if (null != icon) - { - symbol.BinaryRef = icon; - } - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolType)); - } - } - } - - /// - /// Parses a radio button element. - /// - /// Element to parse. - /// Identifier of property referred to by radio button. - /// Relative order of radio buttons. - /// Type of this radio button. - private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var type = RadioButtonType.NotSet; - string value = null; - string x = null; - string y = null; - string width = null; - string height = null; - string text = null; - string tooltip = null; - string help = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Bitmap": - if (RadioButtonType.NotSet != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text")); - } - text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); - type = RadioButtonType.Bitmap; - break; - case "Height": - height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Help": - help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Icon": - if (RadioButtonType.NotSet != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text")); - } - text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); - type = RadioButtonType.Icon; - break; - case "Text": - if (RadioButtonType.NotSet != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon")); - } - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - type = RadioButtonType.Text; - break; - case "ToolTip": - tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Width": - width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "X": - x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Y": - y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - if (null == x) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); - } - - if (null == y) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); - } - - if (null == width) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); - } - - if (null == height) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new RadioButtonSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - Help = (null != tooltip || null != help) ? String.Concat(tooltip, "|", help) : null - }); - - symbol.Set((int)RadioButtonSymbolFields.X, x); - symbol.Set((int)RadioButtonSymbolFields.Y, y); - symbol.Set((int)RadioButtonSymbolFields.Width, width); - symbol.Set((int)RadioButtonSymbolFields.Height, height); - } - - return type; - } - - /// - /// Parses a billboard element. - /// - /// Element to parse. - private void ParseBillboardActionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string action = null; - var order = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - action = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", action); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == action) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Billboard": - order = order + 1; - this.ParseBillboardElement(child, action, order); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a billboard element. - /// - /// Element to parse. - /// Action for the billboard. - /// Order of the billboard. - private void ParseBillboardElement(XElement node, string action, int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string feature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("bil", action, order.ToString(), feature); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Control": - // These are all thrown away. - ControlSymbol lastTabSymbol = null; - string firstControl = null; - string defaultControl = null; - string cancelControl = null; - - this.ParseControlElement(child, id.Id, SymbolDefinitionType.BBControl, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new BillboardSymbol(sourceLineNumbers, id) - { - FeatureRef = feature, - Action = action, - Ordering = order - }); - } - } - - /// - /// Parses a control group element. - /// - /// Element to parse. - /// Symbol type referred to by control group. - /// Expected child elements. - private void ParseControlGroupElement(XElement node, SymbolDefinitionType symbolType, string childTag) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var order = 0; - string property = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - if (childTag != child.Name.LocalName) - { - this.Core.UnexpectedElement(node, child); - } - - switch (child.Name.LocalName) - { - case "ListItem": - this.ParseListItemElement(child, symbolType, property, ref order); - break; - case "Property": - this.ParsePropertyElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - } - - /// - /// Parses a radio button control group element. - /// - /// Element to parse. - /// Property associated with this radio button group. - /// Specifies the current type of radio buttons in the group. - /// The current type of radio buttons in the group. - private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var order = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "RadioButton": - var type = this.ParseRadioButtonElement(child, property, ref order); - if (RadioButtonType.NotSet == groupType) - { - groupType = type; - } - else if (groupType != type) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.RadioButtonTypeInconsistent(childSourceLineNumbers)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - return groupType; - } - - /// - /// Parses an action text element. - /// - /// Element to parse. - private void ParseActionTextElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string action = null; - string message = null; - string template = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Message": - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Template": - template = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == action) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ActionTextSymbol(sourceLineNumbers) - { - Action = action, - Description = message, - Template = template, - }); - } - } - - /// - /// Parses an ui text element. - /// - /// Element to parse. - private void ParseUITextElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string text = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Value": - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("txt", text); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UITextSymbol(sourceLineNumbers, id) - { - Text = text, - }); - } - } - - /// - /// Parses a text style element. - /// - /// Element to parse. - private void ParseTextStyleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - int? red = null; - int? green = null; - int? blue = null; - var bold = false; - var italic = false; - var strike = false; - var underline = false; - string faceName = null; - var size = "0"; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - - // RGB Values - case "Red": - var redColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - if (CompilerConstants.IllegalInteger != redColor) - { - red = redColor; - } - break; - case "Green": - var greenColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - if (CompilerConstants.IllegalInteger != greenColor) - { - green = greenColor; - } - break; - case "Blue": - var blueColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - if (CompilerConstants.IllegalInteger != blueColor) - { - blue = blueColor; - } - break; - - // Style values - case "Bold": - bold = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Italic": - italic = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Strike": - strike = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Underline": - underline = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - - // Font values - case "FaceName": - faceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Size": - size = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.CreateIdentifier("txs", faceName, size.ToString(), (red ?? 0).ToString(), (green ?? 0).ToString(), (blue ?? 0).ToString(), bold.ToString(), italic.ToString(), strike.ToString(), underline.ToString()); - } - - if (null == faceName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new TextStyleSymbol(sourceLineNumbers, id) - { - FaceName = faceName, - LocalizedSize = size, - Red = red, - Green = green, - Blue = blue, - Bold = bold, - Italic = italic, - Strike = strike, - Underline = underline, - }); - } - } - - /// - /// Parses a dialog element. - /// - /// Element to parse. - private void ParseDialogElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var hidden = false; - var modal = true; - var minimize = true; - var customPalette = false; - var errorDialog = false; - var keepModeless = false; - var height = 0; - string title = null; - var leftScroll = false; - var rightAligned = false; - var rightToLeft = false; - var systemModal = false; - var trackDiskSpace = false; - var width = 0; - var x = 50; - var y = 50; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Height": - height = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Title": - title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Width": - width = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "X": - x = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); - break; - case "Y": - y = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); - break; - case "CustomPalette": - customPalette = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ErrorDialog": - errorDialog = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "KeepModeless": - keepModeless = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LeftScroll": - leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Modeless": - modal = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "NoMinimize": - minimize = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightAligned": - rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightToLeft": - rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SystemModal": - systemModal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TrackDiskSpace": - trackDiskSpace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - ControlSymbol lastTabSymbol = null; - string cancelControl = null; - string defaultControl = null; - string firstControl = null; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Control": - this.ParseControlElement(child, id.Id, SymbolDefinitionType.Control, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null != lastTabSymbol && null != lastTabSymbol.Control) - { - if (firstControl != lastTabSymbol.Control) - { - lastTabSymbol.NextControlRef = firstControl; - } - } - - if (null == firstControl) - { - this.Core.Write(ErrorMessages.NoFirstControlSpecified(sourceLineNumbers, id.Id)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new DialogSymbol(sourceLineNumbers, id) - { - HCentering = x, - VCentering = y, - Width = width, - Height = height, - CustomPalette = customPalette, - ErrorDialog = errorDialog, - Visible = !hidden, - Modal = modal, - KeepModeless = keepModeless, - LeftScroll = leftScroll, - Minimize = minimize, - RightAligned = rightAligned, - RightToLeft = rightToLeft, - SystemModal = systemModal, - TrackDiskSpace = trackDiskSpace, - Title = title, - FirstControlRef = firstControl, - DefaultControlRef = defaultControl, - CancelControlRef = cancelControl, - }); - } - } - - /// - /// Parses a control element. - /// - /// Element to parse. - /// Identifier for parent dialog. - /// Table control belongs in. - /// Last control in the tab order. - /// Name of the first control in the tab order. - /// Name of the default control. - /// Name of the candle control. - private void ParseControlElement(XElement node, string dialog, SymbolDefinitionType symbolType, ref ControlSymbol lastTabSymbol, ref string firstControl, ref string defaultControl, ref string cancelControl) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier controlId = null; - var bits = new BitArray(32); - string checkBoxPropertyRef = null; - string checkboxValue = null; - string controlType = null; - var disabled = false; - string height = null; - string help = null; - var isCancel = false; - var isDefault = false; - var notTabbable = false; - string property = null; - var publishOrder = 0; - string sourceFile = null; - string text = null; - string tooltip = null; - var radioButtonsType = RadioButtonType.NotSet; - string width = null; - string x = null; - string y = null; - - string defaultCondition = null; - string enableCondition = null; - string disableCondition = null; - string hideCondition = null; - string showCondition = null; - - var hidden = false; - var sunken = false; - var indirect = false; - var integer = false; - var rightToLeft = false; - var rightAligned = false; - var leftScroll = false; - - // The rest of the method relies on the control's Type, so we have to get that first. - var typeAttribute = node.Attribute("Type"); - if (null == typeAttribute) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); - } - else - { - controlType = this.Core.GetAttributeValue(sourceLineNumbers, typeAttribute); - } - - string[] specialAttributes; - switch (controlType) - { - case "Billboard": - specialAttributes = null; - notTabbable = true; - disabled = true; - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Billboard); - break; - case "Bitmap": - specialAttributes = BitmapControlAttributes; - notTabbable = true; - disabled = true; - break; - case "CheckBox": - specialAttributes = CheckboxControlAttributes; - break; - case "ComboBox": - specialAttributes = ComboboxControlAttributes; - break; - case "DirectoryCombo": - specialAttributes = VolumeControlAttributes; - break; - case "DirectoryList": - specialAttributes = null; - break; - case "Edit": - specialAttributes = EditControlAttributes; - break; - case "GroupBox": - specialAttributes = null; - notTabbable = true; - break; - case "Hyperlink": - specialAttributes = HyperlinkControlAttributes; - break; - case "Icon": - specialAttributes = IconControlAttributes; - notTabbable = true; - disabled = true; - break; - case "Line": - specialAttributes = null; - notTabbable = true; - disabled = true; - break; - case "ListBox": - specialAttributes = ListboxControlAttributes; - break; - case "ListView": - specialAttributes = ListviewControlAttributes; - break; - case "MaskedEdit": - specialAttributes = EditControlAttributes; - break; - case "PathEdit": - specialAttributes = EditControlAttributes; - break; - case "ProgressBar": - specialAttributes = ProgressControlAttributes; - notTabbable = true; - disabled = true; - break; - case "PushButton": - specialAttributes = ButtonControlAttributes; - break; - case "RadioButtonGroup": - specialAttributes = RadioControlAttributes; - break; - case "ScrollableText": - specialAttributes = null; - break; - case "SelectionTree": - specialAttributes = null; - break; - case "Text": - specialAttributes = TextControlAttributes; - notTabbable = true; - break; - case "VolumeCostList": - specialAttributes = VolumeControlAttributes; - notTabbable = true; - break; - case "VolumeSelectCombo": - specialAttributes = VolumeControlAttributes; - break; - default: - specialAttributes = null; - notTabbable = true; - break; - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - controlId = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Type": // already processed - break; - case "Cancel": - isCancel = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "CheckBoxPropertyRef": - checkBoxPropertyRef = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CheckBoxValue": - checkboxValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Default": - isDefault = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DefaultCondition": - defaultCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EnableCondition": - enableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisableCondition": - disableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HideCondition": - hideCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShowCondition": - showCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Height": - height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Help": - help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "IconSize": - var iconSizeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != specialAttributes) - { - switch (iconSizeValue) - { - case "16": - this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); - break; - case "32": - this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); - break; - case "48": - this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); - this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); - break; - } - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type")); - } - break; - case "Property": - property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "TabSkip": - notTabbable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Text": - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ToolTip": - tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Width": - width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "X": - x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Y": - y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Disabled": - disabled = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Sunken": - sunken = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Indirect": - indirect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Integer": - integer = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightToLeft": - rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightAligned": - rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LeftScroll": - leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (null == specialAttributes || !this.Core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16)) - { - this.Core.UnexpectedAttribute(node, attrib); - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var attributes = this.Core.CreateIntegerFromBitArray(bits); - - //if (disabled) - //{ - // attributes |= WindowsInstallerConstants.MsidbControlAttributesEnabled; // bit will be inverted when stored - //} - - if (null == height) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); - } - - if (null == width) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); - } - - if (null == x) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); - } - - if (null == y) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); - } - - if (null == controlId) - { - controlId = this.Core.CreateIdentifier("ctl", dialog, x, y, height, width); - } - - if (isCancel) - { - cancelControl = controlId.Id; - } - - if (isDefault) - { - defaultControl = controlId.Id; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Binary": - this.ParseBinaryElement(child); - break; - case "ComboBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); - break; - case "ListBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); - break; - case "ListView": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "Publish": - this.ParsePublishElement(child, dialog ?? String.Empty, controlId.Id, ref publishOrder); - break; - case "RadioButtonGroup": - radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType); - break; - case "Subscribe": - this.ParseSubscribeElement(child, dialog, controlId.Id); - break; - case "Text": - foreach (var attrib in child.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Value": - text = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(child, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(child, attrib); - } - } - - this.Core.InnerTextDisallowed(child); - - if (!String.IsNullOrEmpty(text) && null != sourceFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value")); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - this.Core.InnerTextDisallowed(node); - - // If the radio buttons have icons, then we need to add the icon attribute. - switch (radioButtonsType) - { - case RadioButtonType.Bitmap: - attributes |= WindowsInstallerConstants.MsidbControlAttributesBitmap; - break; - case RadioButtonType.Icon: - attributes |= WindowsInstallerConstants.MsidbControlAttributesIcon; - break; - case RadioButtonType.Text: - // Text is the default so nothing needs to be added bits - break; - } - - // the logic for creating control rows is a little tricky because of the way tabable controls are set - IntermediateSymbol symbol = null; - if (!this.Core.EncounteredError) - { - if ("CheckBox" == controlType) - { - if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true)); - } - else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef")); - } - else if (!String.IsNullOrEmpty(property)) - { - this.Core.AddSymbol(new CheckBoxSymbol(sourceLineNumbers) - { - Property = property, - Value = checkboxValue, - }); - } - else - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CheckBox, checkBoxPropertyRef); - } - } - - var id = new Identifier(controlId.Access, dialog, controlId.Id); - - if (SymbolDefinitionType.BBControl == symbolType) - { - var bbSymbol = this.Core.AddSymbol(new BBControlSymbol(sourceLineNumbers, id) - { - BillboardRef = dialog, - BBControl = controlId.Id, - Type = controlType, - Attributes = attributes, - Enabled = !disabled, - Indirect = indirect, - Integer = integer, - LeftScroll = leftScroll, - RightAligned = rightAligned, - RightToLeft = rightToLeft, - Sunken = sunken, - Visible = !hidden, - Text = text, - SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } - }); - - bbSymbol.Set((int)BBControlSymbolFields.X, x); - bbSymbol.Set((int)BBControlSymbolFields.Y, y); - bbSymbol.Set((int)BBControlSymbolFields.Width, width); - bbSymbol.Set((int)BBControlSymbolFields.Height, height); - - symbol = bbSymbol; - } - else - { - var controlSymbol = this.Core.AddSymbol(new ControlSymbol(sourceLineNumbers, id) - { - DialogRef = dialog, - Control = controlId.Id, - Type = controlType, - Attributes = attributes, - Enabled = !disabled, - Indirect = indirect, - Integer = integer, - LeftScroll = leftScroll, - RightAligned = rightAligned, - RightToLeft = rightToLeft, - Sunken = sunken, - Visible = !hidden, - Property = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef, - Text = text, - Help = (null == tooltip && null == help) ? null : String.Concat(tooltip, "|", help), // Separator is required, even if only one is non-null.}; - SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } - }); - - controlSymbol.Set((int)BBControlSymbolFields.X, x); - controlSymbol.Set((int)BBControlSymbolFields.Y, y); - controlSymbol.Set((int)BBControlSymbolFields.Width, width); - controlSymbol.Set((int)BBControlSymbolFields.Height, height); - - symbol = controlSymbol; - } - - if (!String.IsNullOrEmpty(defaultCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Default", - Condition = defaultCondition, - }); - } - - if (!String.IsNullOrEmpty(enableCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Enable", - Condition = enableCondition, - }); - } - - if (!String.IsNullOrEmpty(disableCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Disable", - Condition = disableCondition, - }); - } - - if (!String.IsNullOrEmpty(hideCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Hide", - Condition = hideCondition, - }); - } - - if (!String.IsNullOrEmpty(showCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Show", - Condition = showCondition, - }); - } - } - - if (!notTabbable) - { - if (symbol is ControlSymbol controlSymbol) - { - if (null != lastTabSymbol) - { - lastTabSymbol.NextControlRef = controlSymbol.Control; - } - lastTabSymbol = controlSymbol; - } - else if (symbol != null) - { - this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); - } - - if (null == firstControl) - { - firstControl = controlId.Id; - } - } - - // bitmap and icon controls contain a foreign key into the binary table in the text column; - // add a reference if the identifier of the binary entry is known during compilation - if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); - } - } - - /// - /// Parses a publish control event element. - /// - /// Element to parse. - /// Identifier of parent dialog. - /// Identifier of parent control. - /// Relative order of controls. - private void ParsePublishElement(XElement node, string dialog, string control, ref int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string argument = null; - string condition = null; - string controlEvent = null; - string property = null; - - // give this control event a unique ordering - order++; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Control": - if (null != control) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); - } - control = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Dialog": - if (null != dialog) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); - } - dialog = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, dialog); - break; - case "Event": - controlEvent = Compiler.UppercaseFirstChar(this.Core.GetAttributeValue(sourceLineNumbers, attrib)); - break; - case "Order": - order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647); - break; - case "Property": - property = String.Concat("[", this.Core.GetAttributeValue(sourceLineNumbers, attrib), "]"); - break; - case "Value": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == control) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); - } - - if (null == dialog) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog")); - } - - if (null == controlEvent && null == property) // need to specify at least one - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); - } - else if (null != controlEvent && null != property) // cannot specify both - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); - } - - if (null == argument) - { - if (null != controlEvent) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event")); - } - else if (null != property) - { - // if this is setting a property to null, put a special value in the argument column - argument = "{}"; - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ControlEventSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = control, - Event = controlEvent ?? property, - Argument = argument, - Condition = condition, - Ordering = order - }); - } - - if ("DoAction" == controlEvent && null != argument) - { - // if we're not looking at a standard action or a formatted string then create a reference - // to the custom action. - if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); - } - } - - // if we're referring to a dialog but not through a property, add it to the references - if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, argument); - } - } - - /// - /// Parses a control subscription element. - /// - /// Element to parse. - /// Identifier of dialog. - /// Identifier of control. - private void ParseSubscribeElement(XElement node, string dialog, string control) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string controlAttribute = null; - string eventMapping = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Attribute": - controlAttribute = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); - break; - case "Event": - eventMapping = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new EventMappingSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = control, - Event = eventMapping, - Attribute = controlAttribute, - }); - } - } - } -} diff --git a/src/WixToolset.Core/ComponentKeyPath.cs b/src/WixToolset.Core/ComponentKeyPath.cs deleted file mode 100644 index 8e9c5776..00000000 --- a/src/WixToolset.Core/ComponentKeyPath.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class ComponentKeyPath : IComponentKeyPath - { - /// - /// Identifier of the resource to be a key path. - /// - public string Id { get; set; } - - /// - /// Indicates whether the key path was explicitly set for this resource. - /// - public bool Explicit { get; set; } - - /// - /// Type of resource to be the key path. - /// - public PossibleKeyPathType Type { get; set; } - } -} diff --git a/src/WixToolset.Core/DecompileContext.cs b/src/WixToolset.Core/DecompileContext.cs deleted file mode 100644 index a7ec03fd..00000000 --- a/src/WixToolset.Core/DecompileContext.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class DecompileContext : IDecompileContext - { - internal DecompileContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public string DecompilePath { get; set; } - - public OutputType DecompileType { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public string ExtractFolder { get; set; } - - public string CabinetExtractFolder { get; set; } - - public string BaseSourcePath { get; set; } - - public string IntermediateFolder { get; set; } - - public bool IsAdminImage { get; set; } - - public string OutputPath { get; set; } - - public bool SuppressCustomTables { get; set; } - - public bool SuppressDroppingEmptyTables { get; set; } - - public bool SuppressExtractCabinets { get; set; } - - public bool SuppressUI { get; set; } - - public bool TreatProductAsModule { get; set; } - } -} diff --git a/src/WixToolset.Core/DecompileResult.cs b/src/WixToolset.Core/DecompileResult.cs deleted file mode 100644 index fc24cab7..00000000 --- a/src/WixToolset.Core/DecompileResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class DecompileResult : IDecompileResult - { - public XDocument Document { get; set; } - - public IReadOnlyCollection ExtractedFilePaths { get; set; } - - public Platform? Platform { get; set; } - } -} diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs deleted file mode 100644 index 859f582b..00000000 --- a/src/WixToolset.Core/Decompiler.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.Core -{ - using System; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Decompiler of the WiX toolset. - /// - internal class Decompiler : IDecompiler - { - internal Decompiler(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IDecompileResult Decompile(IDecompileContext context) - { - // Pre-decompile. - // - foreach (var extension in context.Extensions) - { - extension.PreDecompile(context); - } - - // Decompile. - // - var result = this.BackendDecompile(context); - - if (result != null) - { - // Post-decompile. - // - foreach (var extension in context.Extensions) - { - extension.PostDecompile(result); - } - } - - return result; - } - - private IDecompileResult BackendDecompile(IDecompileContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendFactories = extensionManager.GetServices(); - - foreach (var factory in backendFactories) - { - if (factory.TryCreateBackend(context.DecompileType.ToString(), context.OutputPath, out var backend)) - { - var result = backend.Decompile(context); - return result; - } - } - - // TODO: messaging that a backend could not be found to decompile the decompile type? - - return null; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs deleted file mode 100644 index cfa78623..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.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.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Core.Bind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BackendHelper : IBackendHelper - { - private 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" }; - - public BackendHelper(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - private IMessaging Messaging { get; } - - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) - { - return new FileFacade(file, assembly); - } - - public IFileFacade CreateFileFacade(FileRow fileRow) - { - return new FileFacade(fileRow); - } - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) - { - return new FileFacade(true, fileSymbol); - } - - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) - { - var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source); - - var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination); - - return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer - { - Source = sourceFullPath, - Destination = destinationFullPath, - Move = move, - SourceLineNumbers = sourceLineNumbers, - Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase) - }; - } - - public string CreateGuid() - { - return Common.GenerateGuid(); - } - - public string CreateGuid(Guid namespaceGuid, string value) - { - return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); - } - - public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) - { - return new ResolvedDirectory - { - DirectoryParent = directoryParent, - Name = name - }; - } - - public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) - { - var command = new ExtractEmbeddedFilesCommand(this, embeddedFiles); - command.Execute(); - - return command.TrackedFiles; - } - - public string GenerateIdentifier(string prefix, params string[] args) - { - return Common.GenerateIdentifier(prefix, args); - } - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) - { - return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); - } - - public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) - { - return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); - } - - public string GetMsiFileName(string value, bool source, bool longName) - { - return Common.GetName(value, source, longName); - } - - public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) - { - var command = new ResolveDelayedFieldsCommand(this.Messaging, delayedFields, variableCache); - command.Execute(); - } - - public string[] SplitMsiFileName(string value) - { - return Common.GetNames(value); - } - - public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) - { - return new TrackedFile(path, type, sourceLineNumbers); - } - - public bool IsValidBinderVariable(string variable) - { - return Common.IsValidBinderVariable(variable); - } - - public bool IsValidFourPartVersion(string version) - { - return Common.IsValidFourPartVersion(version); - } - - public bool IsValidIdentifier(string id) - { - return Common.IsIdentifier(id); - } - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) - { - return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); - } - - public bool IsValidShortFilename(string filename, bool allowWildcards) - { - return Common.IsValidShortFilename(filename, allowWildcards); - } - - private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) - { - try - { - var result = Path.GetFullPath(path); - - var filename = Path.GetFileName(result); - - foreach (var reservedName in ReservedFileNames) - { - if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); - return null; - } - } - - return result; - } - catch (ArgumentException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); - } - catch (PathTooLongException) - { - this.Messaging.Write(ErrorMessages.PathTooLong(sourceLineNumbers, path)); - } - - return null; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs b/src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs deleted file mode 100644 index 2340ed9e..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ExtensionManager : IExtensionManager - { - private const string UserWixFolderName = ".wix4"; - private const string MachineWixFolderName = "WixToolset4"; - private const string ExtensionsFolderName = "extensions"; - - private readonly List extensionFactories = new List(); - private readonly Dictionary> loadedExtensionsByType = new Dictionary>(); - - public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IWixToolsetCoreServiceProvider ServiceProvider { get; } - - public void Add(Assembly extensionAssembly) - { - var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t)); - var factories = types.Select(this.CreateExtensionFactory).ToList(); - - if (!factories.Any()) - { - var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath); - throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory.")); - } - - this.extensionFactories.AddRange(factories); - } - - public void Load(string extensionPath) - { - var checkPath = extensionPath; - var checkedPaths = new List { checkPath }; - try - { - if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath)) - { - if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion)) - { - foreach (var cachePath in this.CacheLocations()) - { - var extensionFolder = Path.Combine(cachePath, extensionId); - - var versionFolder = extensionVersion; - if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder)) - { - checkedPaths.Add(extensionFolder); - continue; - } - - checkPath = Path.Combine(extensionFolder, versionFolder, "tools", extensionId + ".dll"); - checkedPaths.Add(checkPath); - - if (TryLoadFromPath(checkPath, out assembly)) - { - break; - } - } - } - } - - if (assembly == null) - { - throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths)); - } - - this.Add(assembly); - } - catch (ReflectionTypeLoadException rtle) - { - throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString())))); - } - catch (WixException) - { - throw; - } - catch (Exception e) - { - throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e); - } - } - - public IReadOnlyCollection GetServices() where T : class - { - if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions)) - { - extensions = new List(); - - foreach (var factory in this.extensionFactories) - { - if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension) - { - extensions.Add(extension); - } - } - - this.loadedExtensionsByType.Add(typeof(T), extensions); - } - - return extensions.Cast().ToList(); - } - - private IExtensionFactory CreateExtensionFactory(Type type) - { - var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) }); - if (constructor != null) - { - return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider }); - } - - return (IExtensionFactory)Activator.CreateInstance(type); - } - - private IEnumerable CacheLocations() - { - var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - - path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - - if (Environment.Is64BitOperatingSystem) - { - path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - } - - path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - - path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - } - - private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion) - { - extensionId = extensionReference ?? String.Empty; - extensionVersion = String.Empty; - - var index = extensionId.LastIndexOf('/'); - if (index > 0) - { - extensionVersion = extensionReference.Substring(index + 1); - extensionId = extensionReference.Substring(0, index); - - if (!NuGet.Versioning.NuGetVersion.TryParse(extensionVersion, out _)) - { - return false; - } - - if (String.IsNullOrEmpty(extensionId)) - { - return false; - } - } - - return true; - } - - private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder) - { - foundVersionFolder = null; - - try - { - NuGet.Versioning.NuGetVersion version = null; - foreach (var versionPath in Directory.GetDirectories(basePath)) - { - var versionFolder = Path.GetFileName(versionPath); - if (NuGet.Versioning.NuGetVersion.TryParse(versionFolder, out var checkVersion) && - (version == null || version < checkVersion)) - { - foundVersionFolder = versionFolder; - version = checkVersion; - } - } - } - catch (IOException) - { - } - - return !String.IsNullOrEmpty(foundVersionFolder); - } - - private static bool TryLoadFromPath(string extensionPath, out Assembly assembly) - { - try - { - if (File.Exists(extensionPath)) - { - assembly = Assembly.LoadFrom(extensionPath); - return true; - } - } - catch (IOException e) when (e is FileLoadException || e is FileNotFoundException) - { - } - - assembly = null; - return false; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/WixToolset.Core/ExtensibilityServices/FileFacade.cs deleted file mode 100644 index f85d4842..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/FileFacade.cs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - - internal class FileFacade : IFileFacade - { - public FileFacade(FileSymbol file, AssemblySymbol assembly) - { - this.FileSymbol = file; - this.AssemblySymbol = assembly; - - this.Identifier = file.Id; - this.ComponentRef = file.ComponentRef; - } - - public FileFacade(bool fromModule, FileSymbol file) - { - this.FromModule = fromModule; - this.FileSymbol = file; - - this.Identifier = file.Id; - this.ComponentRef = file.ComponentRef; - } - - public FileFacade(FileRow row) - { - this.FromTransform = true; - this.FileRow = row; - - this.Identifier = new Identifier(AccessModifier.Section, row.File); - this.ComponentRef = row.Component; - } - - public bool FromModule { get; } - - public bool FromTransform { get; } - - private FileRow FileRow { get; } - - private FileSymbol FileSymbol { get; } - - private AssemblySymbol AssemblySymbol { get; } - - public string Id => this.Identifier.Id; - - public Identifier Identifier { get; } - - public string ComponentRef { get; } - - public int DiskId - { - get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId; - set - { - if (this.FileRow == null) - { - this.FileSymbol.DiskId = value; - } - else - { - this.FileRow.DiskId = value; - } - } - } - - public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName; - - public int FileSize - { - get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize; - set - { - if (this.FileRow == null) - { - this.FileSymbol.FileSize = value; - } - else - { - this.FileRow.FileSize = value; - } - } - } - - public string Language - { - get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Language = value; - } - else - { - this.FileRow.Language = value; - } - } - } - - public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null; - - public int Sequence - { - get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Sequence = value; - } - else - { - this.FileRow.Sequence = value; - } - } - } - - public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; - - public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; - - public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; - - public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - - public string Version - { - get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Version = value; - } - else - { - this.FileRow.Version = value; - } - } - } - - public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null; - - public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException(); - - public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException(); - - /// - /// Gets the set of MsiAssemblyName rows created for this file. - /// - /// RowCollection of MsiAssemblyName table. - public List AssemblyNames { get; set; } - - /// - /// Gets or sets the MsiFileHash row for this file. - /// - public MsiFileHashSymbol Hash { get; set; } - - /// - /// Allows direct access to the underlying FileRow as requried for patching. - /// - public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs b/src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs deleted file mode 100644 index 2cad7cce..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class FileTransfer : IFileTransfer - { - public string Source { get; set; } - - public string Destination { get; set; } - - public bool Move { get; set; } - - public SourceLineNumber SourceLineNumbers { get; set; } - - public bool Redundant { get; set; } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/Messaging.cs b/src/WixToolset.Core/ExtensibilityServices/Messaging.cs deleted file mode 100644 index afcd9244..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/Messaging.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class Messaging : IMessaging - { - private IMessageListener listener; - private readonly HashSet suppressedWarnings = new HashSet(); - private readonly HashSet warningsAsErrors = new HashSet(); - - public bool EncounteredError { get; private set; } - - public int LastErrorNumber { get; private set; } - - public bool ShowVerboseMessages { get; set; } - - public bool SuppressAllWarnings { get; set; } - - public bool WarningsAsError { get; set; } - - public void ElevateWarningMessage(int warningNumber) => this.warningsAsErrors.Add(warningNumber); - - public void SetListener(IMessageListener listener) => this.listener = listener; - - public void SuppressWarningMessage(int warningNumber) => this.suppressedWarnings.Add(warningNumber); - - public void Write(Message message) - { - var level = this.CalculateMessageLevel(message); - - if (level == MessageLevel.Nothing) - { - return; - } - - if (level == MessageLevel.Error) - { - this.EncounteredError = true; - this.LastErrorNumber = message.Id; - } - - if (this.listener != null) - { - this.listener.Write(message); - } - else if (level == MessageLevel.Error) - { - throw new WixException(message); - } - } - - public void Write(string message, bool verbose = false) - { - if (!verbose || this.ShowVerboseMessages) - { - this.listener?.Write(message); - } - } - - /// - /// Determines the level of this message, when taking into account warning-as-error, - /// warning level, verbosity level and message suppressed by the caller. - /// - /// Event arguments for the message. - /// MessageLevel representing the level of this message. - private MessageLevel CalculateMessageLevel(Message message) - { - var level = message.Level; - - if (level == MessageLevel.Verbose) - { - if (!this.ShowVerboseMessages) - { - level = MessageLevel.Nothing; - } - } - else if (level == MessageLevel.Warning) - { - if (this.SuppressAllWarnings || this.suppressedWarnings.Contains(message.Id)) - { - level = MessageLevel.Nothing; - } - else if (this.WarningsAsError || this.warningsAsErrors.Contains(message.Id)) - { - level = MessageLevel.Error; - } - } - - level = this.listener?.CalculateMessageLevel(this, message, level) ?? level; - - return level; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs deleted file mode 100644 index c1368190..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ /dev/null @@ -1,863 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ParseHelper : IParseHelper - { - public ParseHelper(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = serviceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private ISymbolDefinitionCreator Creator { get; set; } - - public bool ContainsProperty(string possibleProperty) - { - return Common.ContainsProperty(possibleProperty); - } - - public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) - { - - section.AddSymbol(new WixComplexReferenceSymbol(sourceLineNumbers) - { - Parent = parentId, - ParentType = parentType, - ParentLanguage = parentLanguage, - Child = childId, - ChildType = childType, - IsPrimary = isPrimary - }); - - this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); - } - - public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) - { - if (null == id) - { - id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); - } - - var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) - { - ParentDirectoryRef = parentId, - Name = name, - ShortName = shortName, - SourceName = sourceName, - SourceShortName = shortSourceName - }); - - return symbol.Id; - } - - public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) - { - if (String.IsNullOrEmpty(parentId)) - { - throw new ArgumentNullException(nameof(parentId)); - } - - if (String.IsNullOrEmpty(inlineSyntax)) - { - inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true); - } - - if (String.IsNullOrEmpty(inlineSyntax)) - { - return parentId; - } - - inlineSyntax = inlineSyntax.Trim('\\', '/'); - - var cacheKey = String.Concat(parentId, ":", inlineSyntax); - - if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id)) - { - var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax); - - id = identifier.Id; - } - else - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); - } - - return id; - } - - public string CreateGuid(Guid namespaceGuid, string value) - { - return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); - } - - public Identifier CreateIdentifier(string prefix, params string[] args) - { - var id = Common.GenerateIdentifier(prefix, args); - return new Identifier(AccessModifier.Section, id); - } - - public Identifier CreateIdentifierFromFilename(string filename) - { - var id = Common.GetIdentifierFromName(filename); - return new Identifier(AccessModifier.Section, id); - } - - public string CreateIdentifierValueFromPlatform(string name, Platform currentPlatform, BurnPlatforms supportedPlatforms) - { - string suffix = null; - - switch (currentPlatform) - { - case Platform.X86: - if ((supportedPlatforms & BurnPlatforms.X86) == BurnPlatforms.X86) - { - suffix = "_X86"; - } - break; - case Platform.X64: - if ((supportedPlatforms & BurnPlatforms.X64) == BurnPlatforms.X64) - { - suffix = "_X64"; - } - break; - case Platform.ARM64: - if ((supportedPlatforms & BurnPlatforms.ARM64) == BurnPlatforms.ARM64) - { - suffix = "_A64"; - } - break; - } - - return suffix == null ? null : name + suffix; - } - - public Identifier CreateRegistrySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId, bool escapeLeadingHash) - { - if (RegistryRootType.Unknown == root) - { - throw new ArgumentOutOfRangeException(nameof(root)); - } - - if (null == key) - { - throw new ArgumentNullException(nameof(key)); - } - - if (null == componentId) - { - throw new ArgumentNullException(nameof(componentId)); - } - - // Escape the leading '#' character for string registry values. - if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal)) - { - value = String.Concat("#", value); - } - - var id = this.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name)); - - var symbol = section.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root, - Key = key, - Name = name, - Value = value, - ComponentRef = componentId, - }); - - return symbol.Id; - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) - { - section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) - { - Table = symbolName, - PrimaryKeys = primaryKey - }); - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) - { - section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) - { - Table = symbolName, - PrimaryKeys = String.Join("/", primaryKeys) - }); - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) - { - this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKey); - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) - { - this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKeys); - } - - public void CreateWixGroupSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) - { - if (null == parentId || ComplexReferenceParentType.Unknown == parentType) - { - return; - } - - if (null == childId) - { - throw new ArgumentNullException(nameof(childId)); - } - - section.AddSymbol(new WixGroupSymbol(sourceLineNumbers) - { - ParentId = parentId, - ParentType = parentType, - ChildId = childId, - ChildType = childType, - }); - } - - public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) - { - // TODO: verify variable is not a standard bundle variable - if (variable == null) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); - } - - section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id) - { - Variable = variable, - Condition = condition, - BundleExtensionRef = bundleExtensionId, - }); - - if (after != null) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixSearch, after); - // TODO: We're currently defaulting to "always run after", which we will need to change... - this.CreateWixSearchRelationSymbol(section, sourceLineNumbers, id, after, 2); - } - - if (!String.IsNullOrEmpty(bundleExtensionId)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixBundleExtension, bundleExtensionId); - } - } - - public void CreateWixSearchRelationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) - { - section.AddSymbol(new WixSearchRelationSymbol(sourceLineNumbers, id) - { - ParentSearchRef = parentId, - Attributes = attributes, - }); - } - - public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, Identifier identifier = null) - { - if (this.Creator == null) - { - this.CreateSymbolDefinitionCreator(); - } - - if (!this.Creator.TryGetSymbolDefinitionByName(symbolName, out var symbolDefinition)) - { - throw new ArgumentException(nameof(symbolName)); - } - - return this.CreateSymbol(section, sourceLineNumbers, symbolDefinition, identifier); - } - - public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, Identifier identifier = null) - { - return section.AddSymbol(symbolDefinition.CreateSymbol(sourceLineNumbers, identifier)); - } - - public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) - { - section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) - { - Table = tableDefinition.Name, - }); - - // TODO: Check if the given table definition is a custom table. For now we have to assume that it isn't. - //this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableDefinition.Name); - } - - public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName) - { - section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) - { - Table = tableName, - }); - - if (this.Creator == null) - { - this.CreateSymbolDefinitionCreator(); - } - - // TODO: The tableName may not be the same as the symbolName. For now, we have to assume that it is. - // We don't add custom table definitions to the tableDefinitions collection, - // so if it's not in there, it better be a custom table. If the Id is just wrong, - // instead of a custom table, we get an unresolved reference at link time. - if (!this.Creator.TryGetSymbolDefinitionByName(tableName, out var _)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableName); - } - } - - public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) - { - if (null == attribute) - { - throw new ArgumentNullException(nameof(attribute)); - } - - var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; - var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); - - if (String.IsNullOrEmpty(value)) - { - if (canBeEmpty) - { - return String.Empty; - } - } - else - { - if (generatable && value == "*") - { - return value; - } - - if (Guid.TryParse(value, out var guid)) - { - return guid.ToString("B").ToUpperInvariant(); - } - - if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) - { - return value; - } - - if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) || - value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else - { - this.Messaging.Write(ErrorMessages.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return CompilerConstants.IllegalGuid; - } - - public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var access = AccessModifier.Global; - var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); - - var separator = value.IndexOf(' '); - if (separator > 0) - { - var prefix = value.Substring(0, separator); - switch (prefix) - { - case "global": - case "public": - case "package": - access = AccessModifier.Global; - break; - - case "internal": - case "library": - access = AccessModifier.Library; - break; - - case "file": - case "protected": - access = AccessModifier.File; - break; - - case "private": - case "fragment": - case "section": - access = AccessModifier.Section; - break; - - default: - return null; - } - - value = value.Substring(separator + 1).Trim(); - } - - if (!Common.IsIdentifier(value)) - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - return null; - } - else if (72 < value.Length) - { - this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return new Identifier(access, value); - } - - public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); - } - - public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); - } - - public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) - { - if (allowRelative) - { - this.Messaging.Write(ErrorMessages.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else - { - this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - else if (allowRelative) - { - value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value); - } - else if (CompilerCore.IsAmbiguousFilename(value)) - { - this.Messaging.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return value; - } - - public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) - { - Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing."); - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - try - { - var longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat); - - if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue) - { - this.Messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, longValue)); - } - else if (minimum > longValue || maximum < longValue) - { - this.Messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum)); - longValue = CompilerConstants.IllegalLong; - } - - return longValue; - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (OverflowException) - { - this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return CompilerConstants.IllegalLong; - } - - public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) - { - return Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, emptyRule); - } - - public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - if (String.IsNullOrEmpty(value)) - { - return null; - } - - switch (value) - { - case "HKCR": - return RegistryRootType.ClassesRoot; - - case "HKCU": - return RegistryRootType.CurrentUser; - - case "HKLM": - return RegistryRootType.LocalMachine; - - case "HKU": - return RegistryRootType.Users; - - case "HKMU": - if (allowHkmu) - { - return RegistryRootType.MachineUser; - } - break; - } - - if (allowHkmu) - { - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKMU", "HKCR", "HKCU", "HKLM", "HKU")); - } - else - { - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKCR", "HKCU", "HKLM", "HKU")); - } - - return RegistryRootType.Unknown; - } - - public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - if (Version.TryParse(value, out var version)) - { - return version.ToString(); - } - - // Allow versions to contain binder variables. - if (Common.ContainsValidBinderVariable(value)) - { - return value; - } - - this.Messaging.Write(ErrorMessages.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return null; - } - - public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - switch (value) - { - case "yes": - case "true": - return YesNoDefaultType.Yes; - - case "no": - case "false": - return YesNoDefaultType.No; - - case "default": - return YesNoDefaultType.Default; - - default: - this.Messaging.Write(ErrorMessages.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - return YesNoDefaultType.IllegalValue; - } - } - - public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - switch (value) - { - case "yes": - case "true": - return YesNoType.Yes; - - case "no": - case "false": - return YesNoType.No; - - default: - this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - return YesNoType.IllegalValue; - } - } - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) - { - return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); - } - - public SourceLineNumber GetSourceLineNumbers(XElement element) - { - return Preprocessor.GetSourceLineNumbers(element); - } - - public string GetConditionInnerText(XElement element) - { - var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); - - // Return null for a non-existant condition. - return String.IsNullOrEmpty(value) ? null : value; - } - - public string GetTrimmedInnerText(XElement element) - { - var value = Common.GetInnerText(element); - return value?.Trim(); - } - - public void InnerTextDisallowed(XElement element) - { - if (element.Nodes().Any(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType)) - { - var innerText = Common.GetInnerText(element); - if (!String.IsNullOrWhiteSpace(innerText)) - { - var sourceLineNumbers = this.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText)); - } - } - } - - public bool IsValidIdentifier(string value) - { - return Common.IsIdentifier(value); - } - - public bool IsValidLocIdentifier(string identifier) - { - return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc"; - } - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) - { - return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); - } - - public bool IsValidShortFilename(string filename, bool allowWildcards) - { - return Common.IsValidShortFilename(filename, allowWildcards); - } - - public void ParseExtensionAttribute(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary context = null) - { - // Ignore attributes defined by the W3C because we'll assume they are always right. - if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || - attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)) - { - return; - } - - if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension)) - { - extension.ParseAttribute(intermediate, section, element, attribute, context); - } - else - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName)); - } - } - - public void ParseExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context = null) - { - if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) - { - extension.ParseElement(intermediate, section, parentElement, element, context); - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); - } - } - - public IComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) - { - IComponentKeyPath keyPath = null; - - if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) - { - keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context); - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); - } - - return keyPath; - } - - public void ParseForExtensionElements(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element) - { - var checkInnerText = false; - - foreach (var child in element.Nodes()) - { - if (child is XElement childElement) - { - if (element.Name.Namespace == childElement.Name.Namespace) - { - this.UnexpectedElement(element, childElement); - } - else - { - this.ParseExtensionElement(extensions, intermediate, section, element, childElement); - } - } - else - { - checkInnerText = true; - } - } - - if (checkInnerText) - { - this.InnerTextDisallowed(element); - } - } - - public WixActionSymbol ScheduleActionSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition, string beforeAction, string afterAction, bool overridable = false) - { - var actionId = new Identifier(access, sequence, actionName); - - var actionSymbol = section.AddSymbol(new WixActionSymbol(sourceLineNumbers, actionId) - { - SequenceTable = sequence, - Action = actionName, - Condition = condition, - Before = beforeAction, - After = afterAction, - Overridable = overridable, - }); - - if (null != beforeAction) - { - if (WindowsInstallerStandard.IsStandardAction(beforeAction)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction); - } - else - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction); - } - } - - if (null != afterAction) - { - if (WindowsInstallerStandard.IsStandardAction(afterAction)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction); - } - else - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction); - } - } - - return actionSymbol; - } - - public void CreateCustomActionReference(SourceLineNumber sourceLineNumbers, IntermediateSection section, string customAction, Platform currentPlatform, CustomActionPlatforms supportedPlatforms) - { - if (!this.Messaging.EncounteredError) - { - var suffix = "_X86"; - - switch (currentPlatform) - { - case Platform.X64: - if ((supportedPlatforms & CustomActionPlatforms.X64) == CustomActionPlatforms.X64) - { - suffix = "_X64"; - } - break; - case Platform.ARM64: - if ((supportedPlatforms & CustomActionPlatforms.ARM64) == CustomActionPlatforms.ARM64) - { - suffix = "_A64"; - } - break; - } - - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, customAction + suffix); - } - } - - public void UnexpectedAttribute(XElement element, XAttribute attribute) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - Common.UnexpectedAttribute(this.Messaging, sourceLineNumbers, attribute); - } - - public void UnexpectedElement(XElement parentElement, XElement childElement) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement); - this.Messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName)); - } - - private void CreateSymbolDefinitionCreator() - { - this.Creator = this.ServiceProvider.GetService(); - } - - private static bool TryFindExtension(IEnumerable extensions, XNamespace ns, out ICompilerExtension extension) - { - extension = null; - - foreach (var ext in extensions) - { - if (ext.Namespace == ns) - { - extension = ext; - break; - } - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs deleted file mode 100644 index 72be2bcb..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class PathResolver : IPathResolver - { - public string GetCanonicalDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, Platform platform) - { - if (!directories.TryGetValue(directory, out var resolvedDirectory)) - { - throw new WixException(ErrorMessages.ExpectedDirectory(directory)); - } - - if (null == resolvedDirectory.Path) - { - if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) - { - resolvedDirectory.Path = componentIdGenSeeds[directory]; - } - else if (WindowsInstallerStandard.IsStandardDirectory(directory)) - { - resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform); - } - else - { - var name = resolvedDirectory.Name?.ToLowerInvariant(); - - if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) - { - resolvedDirectory.Path = name; - } - else - { - var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform); - - if (null != resolvedDirectory.Name) - { - resolvedDirectory.Path = Path.Combine(parentPath, name); - } - else - { - resolvedDirectory.Path = parentPath; - } - } - } - } - - return resolvedDirectory.Path; - } - - public string GetDirectoryPath(Dictionary directories, string directory) - { - if (!directories.TryGetValue(directory, out var resolvedDirectory)) - { - throw new WixException(ErrorMessages.ExpectedDirectory(directory)); - } - - if (null == resolvedDirectory.Path) - { - var name = resolvedDirectory.Name; - - if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) - { - resolvedDirectory.Path = name; - } - else - { - var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent); - - if (null != resolvedDirectory.Name) - { - resolvedDirectory.Path = Path.Combine(parentPath, name); - } - else - { - resolvedDirectory.Path = parentPath; - } - } - } - - return resolvedDirectory.Path; - } - - public string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) - { - var fileSourcePath = Common.GetName(fileName, true, useLongName); - - if (compressed) - { - // Use just the file name of the file since all uncompressed files must appear - // in the root of the image in a compressed package. - } - else - { - // Get the relative path of where we want the file to be layed out as specified - // in the Directory table. - var directoryPath = this.GetDirectoryPath(directories, directoryId); - fileSourcePath = Path.Combine(directoryPath, fileSourcePath); - } - - // Strip off "SourceDir" if it's still on there. - if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) - { - fileSourcePath = fileSourcePath.Substring(10); - } - - return fileSourcePath; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs deleted file mode 100644 index b0c87bcf..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class PreprocessHelper : IPreprocessHelper - { - private static readonly char[] VariableSplitter = new char[] { '.' }; - private static readonly char[] ArgumentSplitter = new char[] { ',' }; - - public PreprocessHelper(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private Dictionary ExtensionsByPrefix { get; set; } - - public void AddVariable(IPreprocessContext context, string name, string value) - { - this.AddVariable(context, name, value, true); - } - - public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) - { - var currentValue = this.GetVariableValue(context, "var", name); - - if (null == currentValue) - { - context.Variables.Add(name, value); - } - else - { - if (showWarning && value != currentValue) - { - this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); - } - - context.Variables[name] = value; - } - } - - public string EvaluateFunction(IPreprocessContext context, string function) - { - var prefixParts = function.Split(VariableSplitter, 2); - - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) - { - throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); - } - - var prefix = prefixParts[0]; - var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); - - // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. - if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) - { - throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); - } - - var functionName = functionParts[0]; - - // Remove the trailing closing paren. - var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); - - // Parse the arguments and preprocess them. - var args = allArgs.Split(ArgumentSplitter); - for (var i = 0; i < args.Length; i++) - { - args[i] = this.PreprocessString(context, args[i].Trim()); - } - - var result = this.EvaluateFunction(context, prefix, functionName, args); - - // If the function didn't evaluate, try to evaluate the original value as a variable to support - // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. - if (result == null) - { - result = this.GetVariableValue(context, function, true); - } - - return result; - } - - public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(function)) - { - throw new ArgumentNullException("function"); - } - - switch (prefix) - { - case "fun": - switch (function) - { - case "AutoVersion": - // Make sure the base version is specified - if (args.Length == 0 || String.IsNullOrEmpty(args[0])) - { - throw new WixException(ErrorMessages.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); - } - - // Build = days since 1/1/2000; Revision = seconds since midnight / 2 - var now = DateTime.UtcNow; - var build = now - new DateTime(2000, 1, 1); - var revision = now - new DateTime(now.Year, now.Month, now.Day); - - return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); - - default: - return null; - } - - default: - var extensionsByPrefix = this.GetExtensionsByPrefix(); - if (extensionsByPrefix.TryGetValue(prefix, out var extension)) - { - try - { - return extension.EvaluateFunction(prefix, function, args); - } - catch (Exception e) - { - throw new WixException(ErrorMessages.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); - } - } - else - { - return null; - } - } - } - - public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) - { - // Strip the "$(" off the front and the ")" off the back. - if (variable.StartsWith("$(", StringComparison.Ordinal)) - { - variable = variable.Substring(2, variable.Length - 3); - } - - var parts = variable.Split(VariableSplitter, 2); - - if (1 == parts.Length) // missing prefix - { - if (allowMissingPrefix) - { - return this.GetVariableValue(context, "var", parts[0]); - } - else - { - throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); - } - } - else - { - // check for empty variable name - if (0 < parts[1].Length) - { - string result = this.GetVariableValue(context, parts[0], parts[1]); - - // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix - if (null == result && allowMissingPrefix && variable.Contains(".")) - { - result = this.GetVariableValue(context, "var", variable); - } - - return result; - } - else - { - throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); - } - } - } - - public string GetVariableValue(IPreprocessContext context, string prefix, string name) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(name)) - { - throw new ArgumentNullException("name"); - } - - switch (prefix) - { - case "env": - return Environment.GetEnvironmentVariable(name); - - case "sys": - switch (name) - { - case "CURRENTDIR": - return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); - - case "SOURCEFILEDIR": - return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); - - case "SOURCEFILEPATH": - return context.CurrentSourceLineNumber.FileName; - - case "PLATFORM": - this.Messaging.Write(WarningMessages.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); - - goto case "BUILDARCH"; - - case "BUILDARCH": - switch (context.Platform) - { - case Platform.X86: - return "x86"; - - case Platform.X64: - return "x64"; - - case Platform.ARM64: - return "arm64"; - - default: - throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString()); - } - - case "WIXMAJORVERSION": - return ThisAssembly.AssemblyFileVersion.Split('.')[0]; - - case "WIXVERSION": - return ThisAssembly.AssemblyFileVersion; - - default: - return null; - } - - case "var": - return context.Variables.TryGetValue(name, out var result) ? result : null; - - default: - var extensionsByPrefix = this.GetExtensionsByPrefix(); - if (extensionsByPrefix.TryGetValue(prefix, out var extension)) - { - try - { - return extension.GetVariableValue(prefix, name); - } - catch (Exception e) - { - throw new WixException(ErrorMessages.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); - } - } - else - { - return null; - } - } - } - - public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) - { - var prefixParts = pragmaName.Split(VariableSplitter, 2); - - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length) - { - throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); - } - - var prefix = prefixParts[0]; - var pragma = prefixParts[1]; - - if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) - { - throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); - } - - switch (prefix) - { - case "wix": - switch (pragma) - { - // Add any core defined pragmas here - default: - this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); - break; - } - break; - - default: - var extensionsByPrefix = this.GetExtensionsByPrefix(); - if (extensionsByPrefix.TryGetValue(prefix, out var extension)) - { - if (!extension.ProcessPragma(prefix, pragma, args, parent)) - { - this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); - } - } - break; - } - } - - public string PreprocessString(IPreprocessContext context, string value) - { - var sb = new StringBuilder(); - var currentPosition = 0; - var end = 0; - - while (-1 != (currentPosition = value.IndexOf('$', end))) - { - if (end < currentPosition) - { - sb.Append(value, end, currentPosition - end); - } - - end = currentPosition + 1; - - var remainder = value.Substring(end); - if (remainder.StartsWith("$", StringComparison.Ordinal)) - { - sb.Append("$"); - end++; - } - else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) - { - currentPosition = remainder.IndexOf(')'); - if (-1 == currentPosition) - { - this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); - break; - } - - sb.Append("$"); // just put the resource reference back as was - sb.Append(remainder, 0, currentPosition + 1); - - end += currentPosition + 1; - } - else if (remainder.StartsWith("(", StringComparison.Ordinal)) - { - var openParenCount = 1; - var closingParenCount = 0; - var isFunction = false; - var foundClosingParen = false; - - // find the closing paren - int closingParenPosition; - for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) - { - switch (remainder[closingParenPosition]) - { - case '(': - openParenCount++; - isFunction = true; - break; - - case ')': - closingParenCount++; - break; - } - - if (openParenCount == closingParenCount) - { - foundClosingParen = true; - break; - } - } - - // Environment variables may contain parens so if it looks - // like a function, check to see if the environment variable - // prefix was explicitly provided. - if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal)) - { - isFunction = false; - } - - // move the currentPosition to the closing paren - currentPosition += closingParenPosition; - - if (!foundClosingParen) - { - if (isFunction) - { - this.Messaging.Write(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); - break; - } - else - { - this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); - break; - } - } - - var subString = remainder.Substring(1, closingParenPosition - 1); - string result = null; - if (isFunction) - { - result = this.EvaluateFunction(context, subString); - } - else - { - result = this.GetVariableValue(context, subString, true); - } - - if (null == result) - { - if (isFunction) - { - this.Messaging.Write(ErrorMessages.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); - break; - } - else - { - this.Messaging.Write(ErrorMessages.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); - break; - } - } - else - { - if (!isFunction) - { - //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); - } - } - - sb.Append(result); - end += closingParenPosition + 1; - } - else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing - { - sb.Append('$'); - } - } - - if (end < value.Length) - { - sb.Append(value.Substring(end)); - } - - return sb.ToString(); - } - - public void RemoveVariable(IPreprocessContext context, string name) - { - if (!context.Variables.Remove(name)) - { - this.Messaging.Write(ErrorMessages.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); - } - } - - private Dictionary GetExtensionsByPrefix() - { - if (this.ExtensionsByPrefix == null) - { - this.ExtensionsByPrefix = new Dictionary(); - - var extensionManager = this.ServiceProvider.GetService(); - - var extensions = extensionManager.GetServices(); - - foreach (var extension in extensions) - { - if (null != extension.Prefixes) - { - foreach (string prefix in extension.Prefixes) - { - if (!this.ExtensionsByPrefix.ContainsKey(prefix)) - { - this.ExtensionsByPrefix.Add(prefix, extension); - } - } - } - } - } - - return this.ExtensionsByPrefix; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs b/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs deleted file mode 100644 index cc8acfdd..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using WixToolset.Extensibility.Data; - - internal class ResolvedDirectory : IResolvedDirectory - { - public string DirectoryParent { get; set; } - - public string Name { get; set; } - - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs b/src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs deleted file mode 100644 index a2486130..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class SymbolDefinitionCreator : ISymbolDefinitionCreator - { - public SymbolDefinitionCreator(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IServiceProvider ServiceProvider { get; } - - private IEnumerable ExtensionData { get; set; } - - private Dictionary CustomDefinitionByName { get; } = new Dictionary(); - - public void AddCustomSymbolDefinition(IntermediateSymbolDefinition definition) - { - if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision) - { - this.CustomDefinitionByName[definition.Name] = definition; - } - } - - public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) - { - // First, look in the built-ins. - symbolDefinition = SymbolDefinitions.ByName(name); - - if (symbolDefinition == null) - { - if (this.ExtensionData == null) - { - this.LoadExtensionData(); - } - - // Second, look in the extensions. - foreach (var data in this.ExtensionData) - { - if (data.TryGetSymbolDefinitionByName(name, out symbolDefinition)) - { - break; - } - } - - // Finally, look in the custom symbol definitions provided during an intermediate load. - if (symbolDefinition == null) - { - this.CustomDefinitionByName.TryGetValue(name, out symbolDefinition); - } - } - - return symbolDefinition != null; - } - - private void LoadExtensionData() - { - var extensionManager = this.ServiceProvider.GetService(); - - this.ExtensionData = extensionManager.GetServices(); - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs b/src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs deleted file mode 100644 index 028cddbf..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class TrackedFile : ITrackedFile - { - public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers) - { - this.Path = path; - this.Type = type; - this.SourceLineNumbers = sourceLineNumbers; - this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final); - } - - public bool Clean { get; set; } - - public string Path { get; set; } - - public SourceLineNumber SourceLineNumbers { get; set; } - - public TrackedFileType Type { get; set; } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/Uuid.cs b/src/WixToolset.Core/ExtensibilityServices/Uuid.cs deleted file mode 100644 index ad9eea26..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/Uuid.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Net; - using System.Security.Cryptography; - using System.Text; - - /// - /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. - /// - internal static class Uuid - { - /// - /// Creates a version 3 name-based UUID. - /// - /// The namespace UUID. - /// The value. - /// The UUID for the given namespace and value. - public static Guid NewUuid(Guid namespaceGuid, string value) - { - byte[] namespaceBytes = namespaceGuid.ToByteArray(); - short uuidVersion = (short)0x5000; - - // get the fields of the guid which are in host byte ordering - int timeLow = BitConverter.ToInt32(namespaceBytes, 0); - short timeMid = BitConverter.ToInt16(namespaceBytes, 4); - short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6); - - // convert to network byte ordering - timeLow = IPAddress.HostToNetworkOrder(timeLow); - timeMid = IPAddress.HostToNetworkOrder(timeMid); - timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion); - - // get the bytes from the value - byte[] valueBytes = Encoding.Unicode.GetBytes(value); - - // fill-in the hash input buffer - byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length]; - Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4); - Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2); - Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2); - Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8); - Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length); - - // perform the appropriate hash of the namespace and value - byte[] hash; - using (SHA1 sha1 = SHA1.Create()) - { - hash = sha1.ComputeHash(buffer); - } - - // get the fields of the hash which are in network byte ordering - timeLow = BitConverter.ToInt32(hash, 0); - timeMid = BitConverter.ToInt16(hash, 4); - timeHiAndVersion = BitConverter.ToInt16(hash, 6); - - // convert to network byte ordering - timeLow = IPAddress.NetworkToHostOrder(timeLow); - timeMid = IPAddress.NetworkToHostOrder(timeMid); - timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion); - - // set the version and variant bits - timeHiAndVersion &= 0x0FFF; - timeHiAndVersion += uuidVersion; - hash[8] &= 0x3F; - hash[8] |= 0x80; - - // put back the converted values into a 128-bit value - byte[] guidBits = new byte[16]; - Buffer.BlockCopy(hash, 0, guidBits, 0, 16); - - Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4); - Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2); - Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2); - - return new Guid(guidBits); - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/WixBranding.cs b/src/WixToolset.Core/ExtensibilityServices/WixBranding.cs deleted file mode 100644 index 56300400..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/WixBranding.cs +++ /dev/null @@ -1,124 +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. - -using System.Resources; - -[assembly: NeutralResourcesLanguage("en-US")] - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Reflection; - using WixToolset.Extensibility.Services; - - /// - /// Branding strings. - /// - internal class WixBranding : IWixBranding - { - /// - /// News URL for the distribution. - /// - public static string NewsUrl = "http://wixtoolset.org/news/"; - - /// - /// Short product name for the distribution. - /// - public static string ShortProduct = "WiX Toolset"; - - /// - /// Support URL for the distribution. - /// - public static string SupportUrl = "http://wixtoolset.org/"; - - /// - /// Telemetry URL format for the distribution. - /// - public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}"; - - /// - /// VS Extensions Landing page Url for the distribution. - /// - public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; - - public string GetCreatingApplication() - { - return this.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])"); - } - - public string ReplacePlaceholders(string original, Assembly assembly = null) - { - if (assembly == null) - { - assembly = typeof(WixBranding).Assembly; - } - - var commonVersionPath = Path.Combine(Path.GetDirectoryName(typeof(WixBranding).Assembly.Location), "wixver.dll"); - if (File.Exists(commonVersionPath)) - { - var commonFileVersion = FileVersionInfo.GetVersionInfo(commonVersionPath); - - original = original.Replace("[FileCopyright]", commonFileVersion.LegalCopyright); - original = original.Replace("[FileVersion]", commonFileVersion.FileVersion); - } - - var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); - - original = original.Replace("[FileComments]", fileVersion.Comments); - original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); - original = original.Replace("[FileProductName]", fileVersion.ProductName); - original = original.Replace("[FileVersion]", fileVersion.FileVersion); - - if (original.Contains("[FileVersionMajorMinor]")) - { - var version = new Version(fileVersion.FileVersion); - original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); - } - - if (TryGetAttribute(assembly, out AssemblyCompanyAttribute company)) - { - original = original.Replace("[AssemblyCompany]", company.Company); - } - - if (TryGetAttribute(assembly, out AssemblyCopyrightAttribute copyright)) - { - original = original.Replace("[AssemblyCopyright]", copyright.Copyright); - } - - if (TryGetAttribute(assembly, out AssemblyDescriptionAttribute description)) - { - original = original.Replace("[AssemblyDescription]", description.Description); - } - - if (TryGetAttribute(assembly, out AssemblyProductAttribute product)) - { - original = original.Replace("[AssemblyProduct]", product.Product); - } - - if (TryGetAttribute(assembly, out AssemblyTitleAttribute title)) - { - original = original.Replace("[AssemblyTitle]", title.Title); - } - - original = original.Replace("[NewsUrl]", NewsUrl); - original = original.Replace("[ShortProduct]", ShortProduct); - original = original.Replace("[SupportUrl]", SupportUrl); - - return original; - } - - private static bool TryGetAttribute(Assembly assembly, out T attribute) where T : Attribute - { - attribute = null; - - var customAttributes = assembly.GetCustomAttributes(typeof(T), false); - if (null != customAttributes && 0 < customAttributes.Length) - { - attribute = customAttributes[0] as T; - } - - return null != attribute; - } - } -} diff --git a/src/WixToolset.Core/IBinder.cs b/src/WixToolset.Core/IBinder.cs deleted file mode 100644 index a1b66f42..00000000 --- a/src/WixToolset.Core/IBinder.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface IBinder - { - IBindResult Bind(IBindContext context); - } -} diff --git a/src/WixToolset.Core/ICompiler.cs b/src/WixToolset.Core/ICompiler.cs deleted file mode 100644 index 0aae579a..00000000 --- a/src/WixToolset.Core/ICompiler.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ICompiler - { - Intermediate Compile(ICompileContext context); - } -} diff --git a/src/WixToolset.Core/IDecompiler.cs b/src/WixToolset.Core/IDecompiler.cs deleted file mode 100644 index 74ec26de..00000000 --- a/src/WixToolset.Core/IDecompiler.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface IDecompiler - { - IDecompileResult Decompile(IDecompileContext context); - } -} diff --git a/src/WixToolset.Core/ILayoutCreator.cs b/src/WixToolset.Core/ILayoutCreator.cs deleted file mode 100644 index cdff2a78..00000000 --- a/src/WixToolset.Core/ILayoutCreator.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ILayoutCreator - { - void Layout(ILayoutContext context); - } -} diff --git a/src/WixToolset.Core/ILibrarian.cs b/src/WixToolset.Core/ILibrarian.cs deleted file mode 100644 index 0fcedea5..00000000 --- a/src/WixToolset.Core/ILibrarian.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ILibrarian - { - Intermediate Combine(ILibraryContext context); - } -} diff --git a/src/WixToolset.Core/ILinker.cs b/src/WixToolset.Core/ILinker.cs deleted file mode 100644 index 11cc2c87..00000000 --- a/src/WixToolset.Core/ILinker.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ILinker - { - Intermediate Link(ILinkContext context); - } -} diff --git a/src/WixToolset.Core/ILocalizationParser.cs b/src/WixToolset.Core/ILocalizationParser.cs deleted file mode 100644 index 0e70aa0e..00000000 --- a/src/WixToolset.Core/ILocalizationParser.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Xml.Linq; - using WixToolset.Data; - - /// - /// Parses localization source files. - /// - public interface ILocalizationParser - { - /// - /// Loads a localization file from a path on disk. - /// - /// Path to localization file saved on disk. - /// Returns the loaded localization file. - Localization ParseLocalization(string path); - - /// - /// Loads a localization file from memory. - /// - /// Document to parse as localization file. - /// Returns the loaded localization file. - Localization ParseLocalization(XDocument document); - } -} diff --git a/src/WixToolset.Core/IPreprocessor.cs b/src/WixToolset.Core/IPreprocessor.cs deleted file mode 100644 index f6ed5fed..00000000 --- a/src/WixToolset.Core/IPreprocessor.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Xml; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation, move into Extensibility - public interface IPreprocessor - { - IPreprocessResult Preprocess(IPreprocessContext context); - - IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader); - } -} diff --git a/src/WixToolset.Core/IResolver.cs b/src/WixToolset.Core/IResolver.cs deleted file mode 100644 index db25edbe..00000000 --- a/src/WixToolset.Core/IResolver.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Data; - - /// - /// Resolves localization and bind variables. - /// - public interface IResolver - { - /// - /// Resolve localization and bind variables. - /// - /// Resolve context. - /// Resolve result. - IResolveResult Resolve(IResolveContext context); - } -} diff --git a/src/WixToolset.Core/IUnbinder.cs b/src/WixToolset.Core/IUnbinder.cs deleted file mode 100644 index 2b4daaa5..00000000 --- a/src/WixToolset.Core/IUnbinder.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - -#pragma warning disable 1591 // TODO: add documentation, move into Extensibility - public interface IUnbinder - { - Intermediate Unbind(string file, OutputType outputType, string exportBasePath); - } -} diff --git a/src/WixToolset.Core/IncludedFile.cs b/src/WixToolset.Core/IncludedFile.cs deleted file mode 100644 index 25d51191..00000000 --- a/src/WixToolset.Core/IncludedFile.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class IncludedFile : IIncludedFile - { - public string Path { get; set; } - - public SourceLineNumber SourceLineNumbers { get; set; } - } -} diff --git a/src/WixToolset.Core/IncribeContext.cs b/src/WixToolset.Core/IncribeContext.cs deleted file mode 100644 index 9d7055ab..00000000 --- a/src/WixToolset.Core/IncribeContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class InscribeContext : IInscribeContext - { - public InscribeContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - 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/LayoutContext.cs b/src/WixToolset.Core/LayoutContext.cs deleted file mode 100644 index 4b8c7b99..00000000 --- a/src/WixToolset.Core/LayoutContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class LayoutContext : ILayoutContext - { - internal LayoutContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection FileSystemExtensions { get; set; } - - public IReadOnlyCollection FileTransfers { get; set; } - - public IReadOnlyCollection TrackedFiles { get; set; } - - public string IntermediateFolder { get; set; } - - public string ContentsFile { get; set; } - - public string OutputsFile { get; set; } - - public string BuiltOutputsFile { get; set; } - - public bool ResetAcls { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/LayoutCreator.cs b/src/WixToolset.Core/LayoutCreator.cs deleted file mode 100644 index 0c5aaf63..00000000 --- a/src/WixToolset.Core/LayoutCreator.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Bind; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Layout for the WiX toolset. - /// - internal class LayoutCreator : ILayoutCreator - { - internal LayoutCreator(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - private IMessaging Messaging { get; } - - public void Layout(ILayoutContext context) - { - // Pre-layout. - // - foreach (var extension in context.Extensions) - { - extension.PreLayout(context); - } - - try - { - // Final step in binding that transfers (moves/copies) all files generated into the appropriate - // location in the source image. - if (context.FileTransfers?.Any() == true) - { - this.Messaging.Write(VerboseMessages.LayingOutMedia()); - - var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls); - command.Execute(); - } - - if (context.TrackedFiles != null) - { - this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles); - } - } - finally - { - if (context.TrackedFiles != null) - { - if (!String.IsNullOrEmpty(context.ContentsFile)) - { - this.CreateContentsFile(context.ContentsFile, context.TrackedFiles); - } - - if (!String.IsNullOrEmpty(context.OutputsFile)) - { - this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles); - } - - if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) - { - this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles); - } - } - } - - // Post-layout. - foreach (var extension in context.Extensions) - { - extension.PostLayout(); - } - } - - /// - /// Writes the paths to the content files to a text file. - /// - /// Path to write file. - /// Collection of paths to content files that will be written to file. - private void CreateContentsFile(string path, IEnumerable trackedFiles) - { - var uniqueInputFilePaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueInputFilePaths.Any()) - { - return; - } - - var directory = Path.GetDirectoryName(path); - Directory.CreateDirectory(directory); - - using (var contents = new StreamWriter(path, false)) - { - foreach (var inputPath in uniqueInputFilePaths) - { - contents.WriteLine(inputPath); - } - } - } - - /// - /// Writes the paths to the output files to a text file. - /// - /// Path to write file. - /// Collection of files that were transferred to the output directory. - private void CreateOutputsFile(string path, IEnumerable trackedFiles) - { - var uniqueOutputPaths = new SortedSet(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueOutputPaths.Any()) - { - return; - } - - var directory = Path.GetDirectoryName(path); - Directory.CreateDirectory(directory); - - using (var outputs = new StreamWriter(path, false)) - { - //// Don't list files where the source is the same as the destination since - //// that might be the only place the file exists. The outputs file is often - //// used to delete stuff and losing the original source would be bad. - //var uniqueOutputPaths = new SortedSet(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase); - - foreach (var outputPath in uniqueOutputPaths) - { - outputs.WriteLine(outputPath); - } - } - } - - /// - /// Writes the paths to the built output files to a text file. - /// - /// Path to write file. - /// Collection of files that were transferred to the output directory. - private void CreateBuiltOutputsFile(string path, IEnumerable trackedFiles) - { - var uniqueBuiltPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueBuiltPaths.Any()) - { - return; - } - - var directory = Path.GetDirectoryName(path); - Directory.CreateDirectory(directory); - - using (var outputs = new StreamWriter(path, false)) - { - foreach (var builtPath in uniqueBuiltPaths) - { - outputs.WriteLine(builtPath); - } - } - } - - private void CleanTempFiles(string intermediateFolder, IEnumerable trackedFiles) - { - var uniqueTempPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueTempPaths.Any()) - { - return; - } - - var uniqueFolders = new SortedSet(StringComparer.OrdinalIgnoreCase) - { - intermediateFolder - }; - - // Clean up temp files. - foreach (var tempPath in uniqueTempPaths) - { - try - { - this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders); - - File.Delete(tempPath); - } - catch // delete is best effort. - { - } - } - - // Clean up empty temp folders. - foreach (var folder in uniqueFolders.Reverse()) - { - try - { - Directory.Delete(folder); - } - catch // delete is best effort. - { - } - } - } - - private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet uniqueFolders) - { - if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase)) - { - var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length); - - var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); - - folder = intermediateFolder; - - foreach (var part in parts) - { - folder = Path.Combine(folder, part); - - uniqueFolders.Add(folder); - } - } - } - } -} diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs deleted file mode 100644 index 1dd1b44d..00000000 --- a/src/WixToolset.Core/Librarian.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.Core -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Core.Bind; - using WixToolset.Core.Link; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Core librarian tool. - /// - internal class Librarian : ILibrarian - { - internal Librarian(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - /// - /// Create a library by combining several intermediates (objects). - /// - /// Returns the new library. - public Intermediate Combine(ILibraryContext context) - { - if (String.IsNullOrEmpty(context.LibraryId)) - { - context.LibraryId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_'); - } - - foreach (var extension in context.Extensions) - { - extension.PreCombine(context); - } - - Intermediate library = null; - try - { - var sections = context.Intermediates.SelectMany(i => i.Sections).ToList(); - - var collate = new CollateLocalizationsCommand(this.Messaging, context.Localizations); - var localizationsByCulture = collate.Execute(); - - if (this.Messaging.EncounteredError) - { - return null; - } - - this.ResolveFilePathsToEmbed(context, sections); - - foreach (var section in sections) - { - section.AssignToLibrary(context.LibraryId); - } - - library = new Intermediate(context.LibraryId, IntermediateLevels.Compiled, sections, localizationsByCulture); - - library.UpdateLevel(IntermediateLevels.Combined); - - this.Validate(library); - } - finally - { - foreach (var extension in context.Extensions) - { - extension.PostCombine(library); - } - } - - return this.Messaging.EncounteredError ? null : library; - } - - private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable sections) - { - // Resolve paths to files that are to be embedded in the library. - if (context.BindFiles) - { - var variableResolver = this.ServiceProvider.GetService(); - - var fileResolver = new FileResolver(context.BindPaths, context.Extensions); - - foreach (var symbol in sections.SelectMany(s => s.Symbols)) - { - foreach (var field in symbol.Fields.Where(f => f?.Type == IntermediateFieldType.Path)) - { - var pathField = field.AsPath(); - - if (pathField != null && !String.IsNullOrEmpty(pathField.Path)) - { - var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); - - var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); - - if (!String.IsNullOrEmpty(file)) - { - // File was successfully resolved so track the embedded index as the embedded file index. - field.Set(new IntermediateFieldPathValue { Embed = true, Path = file }); - } - else - { - this.Messaging.Write(ErrorMessages.FileNotFound(symbol.SourceLineNumbers, pathField.Path, symbol.Definition.Name)); - } - } - } - } - } - } - - private void Validate(Intermediate library) - { - var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, library.Sections, OutputType.Library); - find.Execute(); - - // TODO: Consider bringing this sort of verification back. - // foreach (Section section in library.Sections) - // { - // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols); - // resolve.Execute(); - // - // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections); - // reportDupes.Execute(); - // } - } - } -} diff --git a/src/WixToolset.Core/LibraryContext.cs b/src/WixToolset.Core/LibraryContext.cs deleted file mode 100644 index e701cadf..00000000 --- a/src/WixToolset.Core/LibraryContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class LibraryContext : ILibraryContext - { - internal LibraryContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IMessaging Messaging { get; set; } - - public bool BindFiles { get; set; } - - public IReadOnlyCollection BindPaths { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public string LibraryId { get; set; } - - public IReadOnlyCollection Localizations { get; set; } - - public IReadOnlyCollection Intermediates { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs b/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs deleted file mode 100644 index d5c69838..00000000 --- a/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class CollateLocalizationsCommand - { - public CollateLocalizationsCommand(IMessaging messaging, IEnumerable localizations) - { - this.Messaging = messaging; - this.Localizations = localizations; - } - - private IMessaging Messaging { get; } - - private IEnumerable Localizations { get; } - - public Dictionary Execute() - { - var localizationsByCulture = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var localization in this.Localizations) - { - if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture)) - { - var merged = this.Merge(existingCulture, localization); - localizationsByCulture[localization.Culture] = merged; - } - else - { - localizationsByCulture.Add(localization.Culture, localization); - } - } - - return localizationsByCulture; - } - - private Localization Merge(Localization existingLocalization, Localization localization) - { - var variables = existingLocalization.Variables.ToDictionary(v => v.Id); - var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - - foreach (var newVariable in localization.Variables) - { - if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable)) - { - variables[newVariable.Id] = newVariable; - } - else if (!newVariable.Overridable) - { - this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id)); - } - } - - foreach (var localizedControl in localization.LocalizedControls) - { - if (!controls.ContainsKey(localizedControl.Key)) - { - controls.Add(localizedControl.Key, localizedControl.Value); - } - } - - return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls); - } - } -} diff --git a/src/WixToolset.Core/Link/ConnectToFeature.cs b/src/WixToolset.Core/Link/ConnectToFeature.cs deleted file mode 100644 index e9a739a1..00000000 --- a/src/WixToolset.Core/Link/ConnectToFeature.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System.Collections.Generic; - using WixToolset.Data; - - /// - /// Object that connects things (components/modules) to features. - /// - internal class ConnectToFeature - { - /// - /// Creates a new connect to feature. - /// - /// Section this connect belongs to. - /// Id of the child. - /// Sets the primary feature for the connection. - /// Sets if this is explicit primary. - public ConnectToFeature(IntermediateSection section, string childId, string primaryFeature, bool explicitPrimaryFeature) - { - this.Section = section; - this.ChildId = childId; - - this.PrimaryFeature = primaryFeature; - this.IsExplicitPrimaryFeature = explicitPrimaryFeature; - } - - /// - /// Gets the section. - /// - /// Section. - public IntermediateSection Section { get; } - - /// - /// Gets the child identifier. - /// - /// The child identifier. - public string ChildId { get; } - - /// - /// Gets or sets if the flag for if the primary feature was set explicitly. - /// - /// The flag for if the primary feature was set explicitly. - public bool IsExplicitPrimaryFeature { get; set; } - - /// - /// Gets or sets the primary feature. - /// - /// The primary feature. - public string PrimaryFeature { get; set; } - - /// - /// Gets the features connected to. - /// - /// Features connected to. - public List ConnectFeatures { get; } = new List(); - } -} diff --git a/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs deleted file mode 100644 index b7874527..00000000 --- a/src/WixToolset.Core/Link/ConnectToFeatureCollection.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.Core.Link -{ - using System; - using System.Collections; - - /// - /// Hash collection of connect to feature objects. - /// - internal class ConnectToFeatureCollection : ICollection - { - private Hashtable collection; - - /// - /// Instantiate a new ConnectToFeatureCollection class. - /// - public ConnectToFeatureCollection() - { - this.collection = new Hashtable(); - } - - /// - /// Gets the number of items in the collection. - /// - /// Number of items in collection. - public int Count - { - get { return this.collection.Count; } - } - - /// - /// Gets if the collection has been synchronized. - /// - /// True if the collection has been synchronized. - public bool IsSynchronized - { - get { return this.collection.IsSynchronized; } - } - - /// - /// Gets the object used to synchronize the collection. - /// - /// Oject used the synchronize the collection. - public object SyncRoot - { - get { return this.collection.SyncRoot; } - } - - /// - /// Gets a feature connection by child id. - /// - /// Identifier of child to locate. - public ConnectToFeature this[string childId] - { - get { return (ConnectToFeature)this.collection[childId]; } - } - - /// - /// Adds a feature connection to the collection. - /// - /// Feature connection to add. - public void Add(ConnectToFeature connection) - { - if (null == connection) - { - throw new ArgumentNullException("connection"); - } - - this.collection.Add(connection.ChildId, connection); - } - - /// - /// Copies the collection into an array. - /// - /// Array to copy the collection into. - /// Index to start copying from. - public void CopyTo(System.Array array, int index) - { - this.collection.CopyTo(array, index); - } - - /// - /// Gets enumerator for the collection. - /// - /// Enumerator for the collection. - public IEnumerator GetEnumerator() - { - return this.collection.Values.GetEnumerator(); - } - } -} diff --git a/src/WixToolset.Core/Link/ConnectToModule.cs b/src/WixToolset.Core/Link/ConnectToModule.cs deleted file mode 100644 index 4380e12c..00000000 --- a/src/WixToolset.Core/Link/ConnectToModule.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.Core.Link -{ - /// - /// Object that connects things to modules. - /// - internal class ConnectToModule - { - private string childId; - private string module; - private string moduleLanguage; - - /// - /// Creates a new connect to module. - /// - /// Id of the child. - /// Id of the module. - /// Language of the module. - public ConnectToModule(string childId, string module, string moduleLanguage) - { - this.childId = childId; - this.module = module; - this.moduleLanguage = moduleLanguage; - } - - /// - /// Gets the id of the child. - /// - /// Child identifier. - public string ChildId - { - get { return this.childId; } - } - - /// - /// Gets the id of the module. - /// - /// The id of the module. - public string Module - { - get { return this.module; } - } - - /// - /// Gets the language of the module. - /// - /// The language of the module. - public string ModuleLanguage - { - get { return this.moduleLanguage; } - } - } -} diff --git a/src/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs deleted file mode 100644 index e0f96ffb..00000000 --- a/src/WixToolset.Core/Link/ConnectToModuleCollection.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.Core.Link -{ - using System; - using System.Collections; - - /// - /// Hash collection of connect to module objects. - /// - internal class ConnectToModuleCollection : ICollection - { - private Hashtable collection; - - /// - /// Instantiate a new ConnectToModuleCollection class. - /// - public ConnectToModuleCollection() - { - this.collection = new Hashtable(); - } - - /// - /// Gets the number of elements actually contained in the ConnectToModuleCollection. - /// - /// The number of elements actually contained in the ConnectToModuleCollection. - public int Count - { - get { return this.collection.Count; } - } - - /// - /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe). - /// - /// true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false. - public bool IsSynchronized - { - get { return this.collection.IsSynchronized; } - } - - /// - /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection. - /// - /// An object that can be used to synchronize access to the ConnectToModuleCollection. - public object SyncRoot - { - get { return this.collection.SyncRoot; } - } - - /// - /// Gets a module connection by child id. - /// - /// Identifier of child to locate. - public ConnectToModule this[string childId] - { - get { return (ConnectToModule)this.collection[childId]; } - } - - /// - /// Adds a module connection to the collection. - /// - /// Module connection to add. - public void Add(ConnectToModule connection) - { - if (null == connection) - { - throw new ArgumentNullException("connection"); - } - - this.collection.Add(connection.ChildId, connection); - } - - /// - /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array. - /// - /// The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing. - /// The zero-based index in array at which copying begins. - public void CopyTo(System.Array array, int index) - { - this.collection.Keys.CopyTo(array, index); - } - - /// - /// Returns an enumerator for the entire ConnectToModuleCollection. - /// - /// An IEnumerator for the entire ConnectToModuleCollection. - public IEnumerator GetEnumerator() - { - return this.collection.Keys.GetEnumerator(); - } - } -} diff --git a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs deleted file mode 100644 index 5d6cc831..00000000 --- a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class FindEntrySectionAndLoadSymbolsCommand - { - public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable sections, OutputType expectedOutpuType) - { - this.Messaging = messaging; - this.Sections = sections; - this.ExpectedOutputType = expectedOutpuType; - } - - private IMessaging Messaging { get; } - - private IEnumerable Sections { get; } - - private OutputType ExpectedOutputType { get; } - - /// - /// Gets the located entry section after the command is executed. - /// - public IntermediateSection EntrySection { get; private set; } - - /// - /// Gets the collection of loaded symbols. - /// - public IDictionary SymbolsByName { get; private set; } - - /// - /// Gets the collection of possibly conflicting symbols. - /// - public IEnumerable PossibleConflicts { get; private set; } - - /// - /// Gets the collection of redundant symbols that should not be included - /// in the final output. - /// - public ISet RedundantSymbols { get; private set; } - - public void Execute() - { - var symbolsByName = new Dictionary(); - var possibleConflicts = new HashSet(); - var redundantSymbols = new HashSet(); - - if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType)) - { - expectedEntrySectionType = SectionType.Unknown; - } - - foreach (var section in this.Sections) - { - // Try to find the one and only entry section. - if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type) - { - // TODO: remove this? - //if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType) - //{ - // string outputExtension = Output.GetExtension(this.ExpectedOutputType); - // this.Messaging.Write(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension)); - //} - - if (null == this.EntrySection) - { - this.EntrySection = section; - } - else - { - this.Messaging.Write(ErrorMessages.MultipleEntrySections(this.EntrySection.Symbols.FirstOrDefault()?.SourceLineNumbers, this.EntrySection.Id, section.Id)); - this.Messaging.Write(ErrorMessages.MultipleEntrySections2(section.Symbols.FirstOrDefault()?.SourceLineNumbers)); - } - } - - // Load all the symbols from the section's tables that create symbols. - foreach (var symbol in section.Symbols.Where(t => t.Id != null)) - { - var symbolWithSection = new SymbolWithSection(section, symbol); - - if (!symbolsByName.TryGetValue(symbolWithSection.Name, out var existingSymbol)) - { - symbolsByName.Add(symbolWithSection.Name, symbolWithSection); - } - else // uh-oh, duplicate symbols. - { - // If the duplicate symbols are both private directories, there is a chance that they - // point to identical symbols. Identical directory symbols are redundant and will not cause - // conflicts. - if (AccessModifier.Section == existingSymbol.Access && AccessModifier.Section == symbolWithSection.Access && - SymbolDefinitionType.Directory == existingSymbol.Symbol.Definition.Type && existingSymbol.Symbol.IsIdentical(symbolWithSection.Symbol)) - { - // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be - // referenced into the final output) it will not add duplicate primary keys during - // the .IDT importing. - existingSymbol.AddRedundant(symbolWithSection); - redundantSymbols.Add(symbolWithSection.Symbol); - } - else - { - symbolWithSection.AddPossibleConflict(existingSymbol); - existingSymbol.AddPossibleConflict(symbolWithSection); - possibleConflicts.Add(symbolWithSection); - } - } - } - } - - this.SymbolsByName = symbolsByName; - this.PossibleConflicts = possibleConflicts; - this.RedundantSymbols = redundantSymbols; - } - } -} diff --git a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs deleted file mode 100644 index 16593c7d..00000000 --- a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class FlattenAndProcessBundleTablesCommand - { - public FlattenAndProcessBundleTablesCommand(IntermediateSection entrySection, IMessaging messaging) - { - this.EntrySection = entrySection; - this.Messaging = messaging; - } - - private IntermediateSection EntrySection { get; } - - private IMessaging Messaging { get; } - - public void Execute() - { - this.FlattenBundleTables(); - - if (this.Messaging.EncounteredError) - { - return; - } - - this.ProcessBundleComplexReferences(); - } - - /// - /// Flattens the tables used in a Bundle. - /// - private void FlattenBundleTables() - { - // We need to flatten the nested PayloadGroups and PackageGroups under - // UX, Chain, and any Containers. When we're done, the WixGroups table - // will hold Payloads under UX, ChainPackages (references?) under Chain, - // and ContainerPackages/Payloads under any authored Containers. - var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging); - - // Create UX payloads and Package payloads and Container packages - groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, - new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); - groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); - groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); - groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); - - // Create Chain packages... - groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); - groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, false); - - groups.RemoveUsedGroupRows(); - } - - private void ProcessBundleComplexReferences() - { - var containersById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); - var groups = this.EntrySection.Symbols.OfType().ToList(); - var payloadsById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); - - var containerByPackage = new Dictionary(); - var referencedPackages = new HashSet(); - var payloadsInBA = new HashSet(); - var payloadsInPackageOrLayout = new HashSet(); - - foreach (var groupSymbol in groups) - { - switch (groupSymbol.ChildType) - { - case ComplexReferenceChildType.ContainerPackage: - switch (groupSymbol.ParentType) - { - case ComplexReferenceParentType.Container: - if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer)) - { - this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id)); - } - else - { - containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]); - } - break; - } - break; - case ComplexReferenceChildType.Package: - switch (groupSymbol.ParentType) - { - case ComplexReferenceParentType.PackageGroup: - if (groupSymbol.ParentId == BurnConstants.BundleChainPackageGroupId) - { - referencedPackages.Add(groupSymbol.ChildId); - } - break; - } - break; - case ComplexReferenceChildType.Payload: - switch (groupSymbol.ParentType) - { - case ComplexReferenceParentType.Container: - if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName) - { - payloadsInBA.Add(groupSymbol.ChildId); - } - break; - case ComplexReferenceParentType.Layout: - payloadsById[groupSymbol.ChildId].LayoutOnly = true; - payloadsInPackageOrLayout.Add(groupSymbol.ChildId); - break; - case ComplexReferenceParentType.Package: - payloadsInPackageOrLayout.Add(groupSymbol.ChildId); - break; - } - break; - } - } - - foreach (var package in this.EntrySection.Symbols.OfType()) - { - if (!referencedPackages.Contains(package.Id.Id)) - { - this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id)); - } - } - - foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType()) - { - if (!referencedPackages.Contains(rollbackBoundary.Id.Id)) - { - this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id)); - } - } - - foreach (var payload in payloadsById.Values) - { - var payloadId = payload.Id.Id; - if (payloadsInBA.Contains(payloadId)) - { - if (payloadsInPackageOrLayout.Contains(payloadId)) - { - this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId)); - } - } - else if (!payloadsInPackageOrLayout.Contains(payloadId)) - { - this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId)); - } - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Assign authored payloads to authored containers. - // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding. - foreach (var groupSymbol in groups) - { - if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container) - { - var payloadSymbol = payloadsById[groupSymbol.ChildId]; - var containerId = groupSymbol.ParentId; - - if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) - { - if (payloadSymbol.Compressed == false) - { - this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); - } - - payloadSymbol.Compressed = true; - payloadSymbol.ContainerRef = containerId; - } - else - { - this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef)); - } - - if (payloadSymbol.LayoutOnly) - { - this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); - } - } - } - - } - } -} diff --git a/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs b/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs deleted file mode 100644 index cbf48abe..00000000 --- a/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using WixToolset.Data; - - internal static class IntermediateSymbolExtensions - { - public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second) - { - var identical = (first.Definition.Type == second.Definition.Type && - (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) && - first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length); - - for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) - { - var firstField = first[i]; - var secondField = second[i]; - - identical = (firstField.AsString() == secondField.AsString()); - } - - return identical; - } - } -} diff --git a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs deleted file mode 100644 index ace2e19d..00000000 --- a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.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.Core.Link -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class ReportConflictingSymbolsCommand - { - public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable possibleConflicts, IEnumerable resolvedSections) - { - this.Messaging = messaging; - this.PossibleConflicts = possibleConflicts; - this.ResolvedSections = resolvedSections; - } - - private IMessaging Messaging { get; } - - private IEnumerable PossibleConflicts { get; } - - private IEnumerable ResolvedSections { get; } - - public void Execute() - { - // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow - // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should - // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting - // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate - // (aka: conflicting) symbol. - var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList(); - if (0 < illegalDuplicates.Count) - { - var referencedSections = new HashSet(this.ResolvedSections); - - foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section))) - { - var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList(); - - if (actuallyReferencedDuplicates.Any()) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, referencedDuplicate.Name)); - - foreach (var duplicate in actuallyReferencedDuplicates) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers)); - } - } - } - } - } - } -} diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs deleted file mode 100644 index efb90bb8..00000000 --- a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Resolves all the simple references in a section. - /// - internal class ResolveReferencesCommand - { - private readonly IntermediateSection entrySection; - private readonly IDictionary symbolsWithSections; - private HashSet referencedSymbols; - private HashSet resolvedSections; - - public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary symbolsWithSections) - { - this.Messaging = messaging; - this.entrySection = entrySection; - this.symbolsWithSections = symbolsWithSections; - this.BuildingMergeModule = (SectionType.Module == entrySection.Type); - } - - public IEnumerable ReferencedSymbolWithSections => this.referencedSymbols; - - public IEnumerable ResolvedSections => this.resolvedSections; - - private bool BuildingMergeModule { get; } - - private IMessaging Messaging { get; } - - /// - /// Resolves all the simple references in a section. - /// - public void Execute() - { - this.resolvedSections = new HashSet(); - this.referencedSymbols = new HashSet(); - - this.RecursivelyResolveReferences(this.entrySection); - } - - /// - /// Recursive helper function to resolve all references of passed in section. - /// - /// Section with references to resolve. - /// Note: recursive function. - private void RecursivelyResolveReferences(IntermediateSection section) - { - // If we already resolved this section, move on to the next. - if (!this.resolvedSections.Add(section)) - { - return; - } - - // Process all of the references contained in this section using the collection of - // symbols provided. Then recursively call this method to process the - // located symbol's section. All in all this is a very simple depth-first - // search of the references per-section. - foreach (var wixSimpleReferenceRow in section.Symbols.OfType()) - { - // If we're building a Merge Module, ignore all references to the Media table - // because Merge Modules don't have Media tables. - if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media") - { - continue; - } - - // See if the symbol (and any of its duplicates) are appropriately accessible. - if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) - { - var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection); - if (accessible.Count == 1) - { - var accessibleSymbol = accessible[0]; - if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section) - { - this.RecursivelyResolveReferences(accessibleSymbol.Section); - } - } - else if (accessible.Count == 0) - { - this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); - } - else // display errors for the duplicate symbols. - { - var accessibleSymbol = accessible[0]; - var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString(); - - if (String.IsNullOrEmpty(referencingSourceLineNumber)) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name)); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber)); - } - - foreach (var accessibleDuplicate in accessible.Skip(1)) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers)); - } - } - } - else - { - this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); - } - } - } - - /// - /// Determine if the symbol and any of its duplicates are accessbile by referencing section. - /// - /// Section referencing the symbol. - /// Symbol being referenced. - /// List of symbols accessible by referencing section. - private List DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) - { - var accessibleSymbols = new List(); - - if (this.AccessibleSymbol(referencingSection, symbolWithSection)) - { - accessibleSymbols.Add(symbolWithSection); - } - - foreach (var dupe in symbolWithSection.PossiblyConflicts) - { - // don't count overridable WixActionSymbols - var symbolAction = symbolWithSection.Symbol as WixActionSymbol; - var dupeAction = dupe.Symbol as WixActionSymbol; - if (symbolAction?.Overridable != dupeAction?.Overridable) - { - continue; - } - - if (this.AccessibleSymbol(referencingSection, dupe)) - { - accessibleSymbols.Add(dupe); - } - } - - foreach (var dupe in symbolWithSection.Redundants) - { - if (this.AccessibleSymbol(referencingSection, dupe)) - { - accessibleSymbols.Add(dupe); - } - } - - return accessibleSymbols; - } - - /// - /// Determine if a single symbol is accessible by the referencing section. - /// - /// Section referencing the symbol. - /// Symbol being referenced. - /// True if symbol is accessible. - private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) - { - switch (symbolWithSection.Access) - { - case AccessModifier.Global: - return true; - case AccessModifier.Library: - return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId); - case AccessModifier.File: - return symbolWithSection.Section.CompilationId == referencingSection.CompilationId; - case AccessModifier.Section: - return referencingSection == symbolWithSection.Section; - default: - throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access)); - } - } - } -} diff --git a/src/WixToolset.Core/Link/SymbolWithSection.cs b/src/WixToolset.Core/Link/SymbolWithSection.cs deleted file mode 100644 index 08e01077..00000000 --- a/src/WixToolset.Core/Link/SymbolWithSection.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.Core.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - - /// - /// Symbol with section representing a single unique symbol. - /// - internal class SymbolWithSection - { - private HashSet possibleConflicts; - private HashSet redundants; - - /// - /// Creates a symbol for a symbol. - /// - /// - /// Symbol for the symbol - public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol) - { - this.Symbol = symbol; - this.Section = section; - this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id); - } - - /// - /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility. - /// - /// Accessbility of the symbol. - public AccessModifier Access => this.Symbol.Id.Access; - - /// - /// Gets the name of the symbol. - /// - /// Name of the symbol. - public string Name { get; } - - /// - /// Gets the symbol for this symbol. - /// - /// Symbol for this symbol. - public IntermediateSymbol Symbol { get; } - - /// - /// Gets the section for the symbol. - /// - /// Section for the symbol. - public IntermediateSection Section { get; } - - /// - /// Gets any duplicates of this symbol with sections that are possible conflicts. - /// - public IEnumerable PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty(); - - /// - /// Gets any duplicates of this symbol with sections that are redundant. - /// - public IEnumerable Redundants => this.redundants ?? Enumerable.Empty(); - - /// - /// Adds a duplicate symbol with sections that is a possible conflict. - /// - /// Symbol with section that is a possible conflict of this symbol. - public void AddPossibleConflict(SymbolWithSection symbolWithSection) - { - if (null == this.possibleConflicts) - { - this.possibleConflicts = new HashSet(); - } - - this.possibleConflicts.Add(symbolWithSection); - } - - /// - /// Adds a duplicate symbol that is redundant. - /// - /// Symbol with section that is redundant of this symbol. - public void AddRedundant(SymbolWithSection symbolWithSection) - { - if (null == this.redundants) - { - this.redundants = new HashSet(); - } - - this.redundants.Add(symbolWithSection); - } - } -} diff --git a/src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs b/src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs deleted file mode 100644 index 2b1925ad..00000000 --- a/src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System; - using WixToolset.Data.Symbols; - - internal static class WixComplexReferenceSymbolExtensions - { - /// - /// Creates a shallow copy of the ComplexReference. - /// - /// A shallow copy of the ComplexReference. - public static WixComplexReferenceSymbol Clone(this WixComplexReferenceSymbol source) - { - var clone = new WixComplexReferenceSymbol(source.SourceLineNumbers, source.Id); - clone.ParentType = source.ParentType; - clone.Parent = source.Parent; - clone.ParentLanguage = source.ParentLanguage; - clone.ChildType = source.ChildType; - clone.Child = source.Child; - clone.IsPrimary = source.IsPrimary; - - return clone; - } - - /// - /// Compares two complex references without considering the primary bit. - /// - /// this - /// Complex reference to compare to. - /// Zero if the objects are equivalent, negative number if the provided object is less, positive if greater. - public static int CompareToWithoutConsideringPrimary(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol other) - { - var comparison = symbol.ChildType - other.ChildType; - if (0 == comparison) - { - comparison = String.Compare(symbol.Child, other.Child, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = symbol.ParentType - other.ParentType; - if (0 == comparison) - { - string thisParentLanguage = null == symbol.ParentLanguage ? String.Empty : symbol.ParentLanguage; - string otherParentLanguage = null == other.ParentLanguage ? String.Empty : other.ParentLanguage; - comparison = String.Compare(thisParentLanguage, otherParentLanguage, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = String.Compare(symbol.Parent, other.Parent, StringComparison.Ordinal); - } - } - } - } - - return comparison; - } - - /// - /// Changes all of the parent references to point to the passed in parent reference. - /// - /// this - /// New parent complex reference. - public static void Reparent(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol parent) - { - symbol.Parent = parent.Parent; - symbol.ParentLanguage = parent.ParentLanguage; - symbol.ParentType = parent.ParentType; - - if (!symbol.IsPrimary) - { - symbol.IsPrimary = parent.IsPrimary; - } - } - } -} diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs deleted file mode 100644 index f9de82a9..00000000 --- a/src/WixToolset.Core/Link/WixGroupingOrdering.cs +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Link -{ - using System; - using System.Collections.ObjectModel; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - using WixToolset.Data.Burn; - - /// - /// Grouping and Ordering class of the WiX toolset. - /// - internal class WixGroupingOrdering - { - private readonly IMessaging Messaging; - private List groupTypes; - private List itemTypes; - private ItemCollection items; - private readonly List symbolsUsed; - private bool loaded; - - /// - /// Creates a WixGroupingOrdering object. - /// - /// Output from which to read the group and order information. - /// Handler for any error messages. - public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) - { - this.EntrySection = entrySections; - this.Messaging = messageHandler; - - this.symbolsUsed = new List(); - this.loaded = false; - } - - private IntermediateSection EntrySection { get; } - - /// - /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. - /// - /// Group types to include. - /// Item types to include. - public void UseTypes(IEnumerable groupTypes, IEnumerable itemTypes) - { - this.groupTypes = new List(groupTypes.Select(g => g.ToString())); - this.itemTypes = new List(itemTypes.Select(i => i.ToString())); - - this.items = new ItemCollection(); - this.loaded = false; - } - - /// - /// Finds all nested items under a parent group and creates new WixGroup data for them. - /// - /// The group type for the parent group to flatten. - /// The identifier of the parent group to flatten. - /// Whether to remove used group rows before returning. - public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows) - { - var parentTypeString = parentType.ToString(); - Debug.Assert(this.groupTypes.Contains(parentTypeString)); - - this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); - if (this.Messaging.EncounteredError) - { - return; - } - - this.CreateNewGroupRows(parentTypeString, parentId, orderedItems); - - if (removeUsedRows) - { - this.RemoveUsedGroupRows(); - } - } - - /// - /// Finds all items under a parent group type and creates new WixGroup data for them. - /// - /// The type of the parent group to flatten. - /// Whether to remove used group rows before returning. - public void FlattenAndRewriteGroups(ComplexReferenceParentType parentType, bool removeUsedRows) - { - var parentTypeString = parentType.ToString(); - Debug.Assert(this.groupTypes.Contains(parentTypeString)); - - this.LoadFlattenOrderGroups(); - if (this.Messaging.EncounteredError) - { - return; - } - - foreach (Item item in this.items) - { - if (parentTypeString == item.Type) - { - this.CreateOrderedList(item.Type, item.Id, out var orderedItems); - this.CreateNewGroupRows(item.Type, item.Id, orderedItems); - } - } - - if (removeUsedRows) - { - this.RemoveUsedGroupRows(); - } - } - - - /// - /// Creates a flattened and ordered list of items for the given parent group. - /// - /// The group type for the parent group to flatten. - /// The identifier of the parent group to flatten. - /// The returned list of ordered items. - private void CreateOrderedList(string parentType, string parentId, out List orderedItems) - { - orderedItems = null; - - this.LoadFlattenOrderGroups(); - if (this.Messaging.EncounteredError) - { - return; - } - - if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); - return; - } - - orderedItems = new List(parentItem.ChildItems); - orderedItems.Sort(new Item.AfterItemComparer()); - } - - /// - /// Removes rows from WixGroup that have been used by this object. - /// - public void RemoveUsedGroupRows() - { - foreach (var symbol in this.symbolsUsed) - { - this.EntrySection.RemoveSymbol(symbol); - } - } - - /// - /// Creates new WixGroup rows for a list of items. - /// - /// The group type for the parent group in the new rows. - /// The identifier of the parent group in the new rows. - /// The list of new items. - private void CreateNewGroupRows(string parentType, string parentId, List orderedItems) - { - // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither - // does WiX (although they do, currently). We probably want to "upgrade" this to a new - // table that includes a sequence number, and then change the code that uses ordered - // groups to read from that table instead. - foreach (var item in orderedItems) - { - this.EntrySection.AddSymbol(new WixGroupSymbol(item.Row.SourceLineNumbers) - { - ParentId = parentId, - ParentType = (ComplexReferenceParentType)Enum.Parse(typeof(ComplexReferenceParentType), parentType), - ChildId = item.Id, - ChildType = (ComplexReferenceChildType)Enum.Parse(typeof(ComplexReferenceChildType), item.Type), - }); - } - } - - // Group/Ordering Flattening Logic - // - // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in - // play: grouping (parent/child relationships) and ordering (before/after relationships). - // Dealing with just one or the other is straghtforward. Groups can be flattened - // recursively. Ordering can be propagated in either direction. When the ordering also - // participates in the grouping constructions, however, things get trickier. For the - // purposes of this discussion, we're dealing with "items" and "groups", and an instance - // of either of them can be marked as coming "after" some other instance. - // - // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, - // and B is after C, then we can say that A is after *both* B and C. If a group is involved, - // it acts as a proxy for all of its included items and any sub-groups. - - /// - /// Internal workhorse for ensuring that group and ordering information has - /// been loaded and applied. - /// - private void LoadFlattenOrderGroups() - { - if (!this.loaded) - { - this.LoadGroups(); - this.LoadOrdering(); - - // It would be really nice to have a "find circular after dependencies" - // function, but it gets much more complicated because of the way that - // the dependencies are propagated across group boundaries. For now, we - // just live with the dependency loop detection as we flatten the - // dependencies. Group references, however, we can check directly. - this.FindCircularGroupReferences(); - - if (!this.Messaging.EncounteredError) - { - this.FlattenGroups(); - this.FlattenOrdering(); - } - - this.loaded = true; - } - } - - /// - /// Loads data from the WixGroup table. - /// - private void LoadGroups() - { - //Table wixGroupTable = this.output.Tables["WixGroup"]; - //if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) - //{ - // // TODO: Change message name to make it *not* Bundle specific? - // this.Write(WixErrors.MissingBundleInformation("WixGroup")); - //} - - // Collect all of the groups - foreach (var symbol in this.EntrySection.Symbols.OfType()) - { - var rowParentName = symbol.ParentId; - var rowParentType = symbol.ParentType.ToString(); - var rowChildName = symbol.ChildId; - var rowChildType = symbol.ChildType.ToString(); - - // If this row specifies a parent or child type that's not in our - // lists, we assume it's not a row that we're concerned about. - if (!this.groupTypes.Contains(rowParentType) || - !this.itemTypes.Contains(rowChildType)) - { - continue; - } - - this.symbolsUsed.Add(symbol); - - if (!this.items.TryGetValue(rowParentType, rowParentName, out var parentItem)) - { - parentItem = new Item(symbol, rowParentType, rowParentName); - this.items.Add(parentItem); - } - - if (!this.items.TryGetValue(rowChildType, rowChildName, out var childItem)) - { - childItem = new Item(symbol, rowChildType, rowChildName); - this.items.Add(childItem); - } - - parentItem.ChildItems.Add(childItem); - } - } - - /// - /// Flattens group/item information. - /// - private void FlattenGroups() - { - foreach (Item item in this.items) - { - item.FlattenChildItems(); - } - } - - /// - /// Finds and reports circular references in the group/item data. - /// - private void FindCircularGroupReferences() - { - ItemCollection itemsInKnownLoops = new ItemCollection(); - foreach (Item item in this.items) - { - if (itemsInKnownLoops.Contains(item)) - { - continue; - } - - ItemCollection itemsSeen = new ItemCollection(); - string circularReference; - if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) - { - itemsInKnownLoops.Add(itemsSeen); - this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); - } - } - } - - /// - /// Recursive worker to find and report circular references in group/item data. - /// - /// The sentinal item being checked. - /// The current item in the recursion. - /// A list of all items already visited (for performance). - /// A list of items in the current circular reference, if one was found; null otherwise. - /// True if a circular reference was found; false otherwise. - private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) - { - circularReference = null; - foreach (Item subitem in currentItem.ChildItems) - { - if (checkItem == subitem) - { - // TODO: Even better would be to include the source lines for each reference! - circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", - currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); - return true; - } - - if (!itemsSeen.Contains(subitem)) - { - itemsSeen.Add(subitem); - if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) - { - // TODO: Even better would be to include the source lines for each reference! - circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", - currentItem.Type, currentItem.Id, circularReference); - return true; - } - } - } - - return false; - } - - /// - /// Loads ordering dependency data from the WixOrdering table. - /// - private void LoadOrdering() - { - //Table wixOrderingTable = output.Tables["WixOrdering"]; - //if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) - //{ - // // TODO: Do we need a message here? - // return; - //} - - foreach (var row in this.EntrySection.Symbols.OfType()) - { - var rowItemType = row.ItemType.ToString(); - var rowItemName = row.ItemIdRef; - var rowDependsOnType = row.DependsOnType.ToString(); - var rowDependsOnName = row.DependsOnIdRef; - - // If this row specifies some other (unknown) type in either - // position, we assume it's not a row that we're concerned about. - // For ordering, we allow group and item in either position. - if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || - !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) - { - continue; - } - - if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); - } - - if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); - } - - if (null == item || null == dependsOn) - { - continue; - } - - item.AddAfter(dependsOn, this.Messaging); - } - } - - /// - /// Flattens the ordering dependencies in the groups/items. - /// - private void FlattenOrdering() - { - // Because items don't know about their parent groups (and can, in fact, be - // in more than one group at a time), we need to pre-propagate the 'afters' - // from each parent item to its children before we attempt to flatten the - // ordering. - foreach (Item item in this.items) - { - item.PropagateAfterToChildItems(this.Messaging); - } - - foreach (Item item in this.items) - { - item.FlattenAfters(this.Messaging); - } - } - - /// - /// A variant of KeyedCollection that doesn't throw when an item is re-added. - /// - /// Key type for the collection. - /// Item type for the colelction. - internal abstract class EnhancedKeyCollection : KeyedCollection - { - new public void Add(TItem item) - { - if (!this.Contains(item)) - { - base.Add(item); - } - } - - public void Add(Collection list) - { - foreach (TItem item in list) - { - this.Add(item); - } - } - - public void Remove(Collection list) - { - foreach (TItem item in list) - { - this.Remove(item); - } - } - - public bool TryGetValue(TKey key, out TItem item) - { - // KeyedCollection doesn't implement the TryGetValue() method, but it's - // a useful concept. We can't just always pass this to the enclosed - // Dictionary, however, because it doesn't always exist! If it does, we - // can delegate to it as one would expect. If it doesn't, we have to - // implement everything ourselves in terms of Contains(). - - if (null != this.Dictionary) - { - return this.Dictionary.TryGetValue(key, out item); - } - - if (this.Contains(key)) - { - item = this[key]; - return true; - } - - item = default(TItem); - return false; - } - -#if DEBUG - // This just makes debugging easier... - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - foreach (TItem item in this) - { - sb.AppendFormat("{0}, ", item); - } - sb.Length -= 2; - return sb.ToString(); - } -#endif // DEBUG - } - - /// - /// A specialized EnhancedKeyCollection, typed to Items. - /// - internal class ItemCollection : EnhancedKeyCollection - { - protected override string GetKeyForItem(Item item) - { - return item.Key; - } - - public bool TryGetValue(string type, string id, out Item item) - { - return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); - } - - public static string CreateKeyFromTypeId(string type, string id) - { - return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); - } - } - - /// - /// An item (or group) in the grouping/ordering engine. - /// - /// Encapsulates nested group membership and also before/after - /// ordering dependencies. - internal class Item - { - private readonly ItemCollection afterItems; - private readonly ItemCollection beforeItems; // for checking for circular references - private bool flattenedAfterItems; - - public Item(IntermediateSymbol row, string type, string id) - { - this.Row = row; - this.Type = type; - this.Id = id; - - this.Key = ItemCollection.CreateKeyFromTypeId(type, id); - - this.afterItems = new ItemCollection(); - this.beforeItems = new ItemCollection(); - this.flattenedAfterItems = false; - } - - public IntermediateSymbol Row { get; private set; } - public string Type { get; private set; } - public string Id { get; private set; } - public string Key { get; private set; } - -#if DEBUG - // Makes debugging easier... - public override string ToString() - { - return this.Key; - } -#endif // DEBUG - - public ItemCollection ChildItems { get; } = new ItemCollection(); - - /// - /// Removes any nested groups under this item and replaces - /// them with their child items. - /// - public void FlattenChildItems() - { - ItemCollection flattenedChildItems = new ItemCollection(); - - foreach (Item childItem in this.ChildItems) - { - if (0 == childItem.ChildItems.Count) - { - flattenedChildItems.Add(childItem); - } - else - { - childItem.FlattenChildItems(); - flattenedChildItems.Add(childItem.ChildItems); - } - } - - this.ChildItems.Clear(); - this.ChildItems.Add(flattenedChildItems); - } - - /// - /// Adds a list of items to the 'after' ordering collection. - /// - /// List of items to add. - /// Message handler in case a circular ordering reference is found. - public void AddAfter(ItemCollection items, IMessaging messageHandler) - { - foreach (Item item in items) - { - this.AddAfter(item, messageHandler); - } - } - - /// - /// Adds an item to the 'after' ordering collection. - /// - /// Item to add. - /// Message handler in case a circular ordering reference is found. - public void AddAfter(Item after, IMessaging messageHandler) - { - if (this.beforeItems.Contains(after)) - { - // We could try to chain this up (the way that group circular dependencies - // are reported), but since we're in the process of flattening, we may already - // have lost some distinction between authored and propagated ordering. - string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", - this.Type, this.Id, after.Type, after.Id); - messageHandler.Write(ErrorMessages.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); - return; - } - - this.afterItems.Add(after); - after.beforeItems.Add(this); - } - - /// - /// Propagates 'after' dependencies from an item to its child items. - /// - /// Message handler in case a circular ordering reference is found. - /// Because items don't know about their parent groups (and can, in fact, be in more - /// than one group at a time), we need to propagate the 'afters' from each parent item to its children - /// before we attempt to flatten the ordering. - public void PropagateAfterToChildItems(IMessaging messageHandler) - { - if (this.ShouldItemPropagateChildOrdering()) - { - foreach (Item childItem in this.ChildItems) - { - childItem.AddAfter(this.afterItems, messageHandler); - } - } - } - - /// - /// Flattens the ordering dependency for this item. - /// - /// Message handler in case a circular ordering reference is found. - public void FlattenAfters(IMessaging messageHandler) - { - if (this.flattenedAfterItems) - { - return; - } - - this.flattenedAfterItems = true; - - // Ensure that if we're after something (A), and *it's* after something (B), - // that we list ourselved as after both (A) *and* (B). - ItemCollection nestedAfterItems = new ItemCollection(); - - foreach (Item afterItem in this.afterItems) - { - afterItem.FlattenAfters(messageHandler); - nestedAfterItems.Add(afterItem.afterItems); - - if (afterItem.ShouldItemPropagateChildOrdering()) - { - // If we are after a group, it really means - // we are after all of the group's children. - foreach (Item childItem in afterItem.ChildItems) - { - childItem.FlattenAfters(messageHandler); - nestedAfterItems.Add(childItem.afterItems); - nestedAfterItems.Add(childItem); - } - } - } - - this.AddAfter(nestedAfterItems, messageHandler); - } - - // We *don't* propagate ordering information from Packages or - // Containers to their children, because ordering doesn't matter - // for them, and a Payload in two Packages (or Containers) can - // cause a circular reference to occur. - private bool ShouldItemPropagateChildOrdering() - { - if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) || - String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal)) - { - return false; - } - return true; - } - - /// - /// Helper IComparer class to make ordering easier. - /// - internal class AfterItemComparer : IComparer - { - public int Compare(Item x, Item y) - { - if (x.afterItems.Contains(y)) - { - return 1; - } - else if (y.afterItems.Contains(x)) - { - return -1; - } - - return String.CompareOrdinal(x.Id, y.Id); - } - } - } - } -} diff --git a/src/WixToolset.Core/LinkContext.cs b/src/WixToolset.Core/LinkContext.cs deleted file mode 100644 index b99bb9c4..00000000 --- a/src/WixToolset.Core/LinkContext.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.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class LinkContext : ILinkContext - { - internal LinkContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection ExtensionData { get; set; } - - public OutputType ExpectedOutputType { get; set; } - - public IReadOnlyCollection Intermediates { get; set; } - - public ISymbolDefinitionCreator SymbolDefinitionCreator { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs deleted file mode 100644 index 47671f26..00000000 --- a/src/WixToolset.Core/Linker.cs +++ /dev/null @@ -1,942 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using WixToolset.Core.Link; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Linker core of the WiX toolset. - /// - internal class Linker : ILinker - { - private static readonly string EmptyGuid = Guid.Empty.ToString("B"); - - private readonly bool sectionIdOnRows; - - /// - /// Creates a linker. - /// - internal Linker(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = this.ServiceProvider.GetService(); - this.sectionIdOnRows = true; // TODO: what is the correct value for this? - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private ILinkContext Context { get; set; } - - /// - /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. - /// - /// The path to output the xml file. - public string UnreferencedSymbolsFile { get; set; } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages { get; set; } - - /// - /// Links a collection of sections into an output. - /// - /// Output intermediate from the linking. - public Intermediate Link(ILinkContext context) - { - this.Context = context; - - if (this.Context.SymbolDefinitionCreator == null) - { - this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService(); - } - - foreach (var extension in this.Context.Extensions) - { - extension.PreLink(this.Context); - } - - var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled)); - if (invalidIntermediates.Any()) - { - this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id)))); - } - - Intermediate intermediate = null; - try - { - var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); - var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList(); - - // Add sections from the extensions with data. - foreach (var data in this.Context.ExtensionData) - { - var library = data.GetLibrary(this.Context.SymbolDefinitionCreator); - - if (library != null) - { - sections.AddRange(library.Sections); - } - } - - //this.activeOutput = null; - - var multipleFeatureComponents = new Hashtable(); - - var wixVariables = new Dictionary(); - - // First find the entry section and while processing all sections load all the symbols from all of the sections. - var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); - find.Execute(); - - // Must have found the entry section by now. - if (null == find.EntrySection) - { - if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown) - { - throw new WixException(ErrorMessages.MissingEntrySection()); - } - else - { - throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); - } - } - - // Add the missing standard action and directory symbols. - this.LoadStandardSymbols(find.SymbolsByName); - - // Resolve the symbol references to find the set of sections we care about for linking. - // Of course, we start with the entry section (that's how it got its name after all). - var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); - resolve.Execute(); - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Reset the sections to only those that were resolved then flatten the complex - // references that particpate in groups. - sections = resolve.ResolvedSections.ToList(); - - // TODO: consider filtering "localizations" down to only those localizations from - // intermediates in the sections. - - this.FlattenSectionsComplexReferences(sections); - - if (this.Messaging.EncounteredError) - { - return null; - } - - // The hard part in linking is processing the complex references. - var referencedComponents = new HashSet(); - var componentsToFeatures = new ConnectToFeatureCollection(); - var featuresToFeatures = new ConnectToFeatureCollection(); - var modulesToFeatures = new ConnectToFeatureCollection(); - this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Display an error message for Components that were not referenced by a Feature. - foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component)) - { - if (!referencedComponents.Contains(symbolWithSection.Name)) - { - this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id)); - } - } - - // Report duplicates that would ultimately end up being primary key collisions. - { - var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); - reportDupes.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - // resolve the feature to feature connects - this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); - - // Create a new section to hold the linked content. Start with the entry section's - // metadata. - var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); - - var sectionCount = 0; - - foreach (var section in sections) - { - sectionCount++; - - var sectionId = section.Id; - if (null == sectionId && this.sectionIdOnRows) - { - sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); - } - - foreach (var symbol in section.Symbols) - { - if (find.RedundantSymbols.Contains(symbol)) - { - continue; - } - - var copySymbol = true; // by default, copy symbols. - - // handle special tables - switch (symbol.Definition.Type) - { - case SymbolDefinitionType.Class: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.Extension: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.Assembly: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.PublishComponent: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.Shortcut: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.TypeLib: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.WixMerge: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null); - } - break; - - case SymbolDefinitionType.WixComplexReference: - copySymbol = false; - break; - - case SymbolDefinitionType.WixSimpleReference: - copySymbol = false; - break; - - case SymbolDefinitionType.WixVariable: - this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); - copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. - break; - } - - if (copySymbol) - { - resolvedSection.AddSymbol(symbol); - } - } - } - - // Copy the module to feature connections into the output. - foreach (ConnectToFeature connectToFeature in modulesToFeatures) - { - foreach (var feature in connectToFeature.ConnectFeatures) - { - resolvedSection.AddSymbol(new WixFeatureModulesSymbol - { - FeatureRef = feature, - WixMergeRef = connectToFeature.ChildId - }); - } - } - - // Correct the section Id in FeatureComponents table. - if (this.sectionIdOnRows) - { -#if TODO_DO_SYMBOLS_NEED_SECTIONIDS - var componentSectionIds = resolvedSection.Symbols.OfType().ToDictionary(c => c.Id.Id, c => c.SectionId); - - foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType()) - { - if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) - { - featureComponentSymbol.SectionId = componentSectionId; - } - } -#endif - } - - // Copy the wix variable rows to the output now that all overriding has been accounted for. - foreach (var symbol in wixVariables.Values) - { - resolvedSection.AddSymbol(symbol); - } - - // Bundles have groups of data that must be flattened in a way different from other types. - if (resolvedSection.Type == SectionType.Bundle) - { - var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - var collate = new CollateLocalizationsCommand(this.Messaging, localizations); - var localizationsByCulture = collate.Execute(); - - intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); - } - finally - { - foreach (var extension in this.Context.Extensions) - { - extension.PostLink(intermediate); - } - } - - return this.Messaging.EncounteredError ? null : intermediate; - } - - /// - /// Check for colliding values and collect the wix variable rows. - /// - /// Collection of WixVariableSymbols by id. - /// WixVariableSymbol to add, if not overridden. - private void AddWixVariable(Dictionary wixVariables, WixVariableSymbol symbol) - { - var id = symbol.Id.Id; - - if (wixVariables.TryGetValue(id, out var collidingSymbol)) - { - if (collidingSymbol.Overridable && !symbol.Overridable) - { - wixVariables[id] = symbol; - } - else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) - { - this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); - } - } - else - { - wixVariables.Add(id, symbol); - } - } - - /// - /// Load the standard action and directory symbols. - /// - /// Collection of symbols. - private void LoadStandardSymbols(IDictionary symbolsByName) - { - foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) - { - var symbolWithSection = new SymbolWithSection(null, actionSymbol); - - // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. - if (!symbolsByName.ContainsKey(symbolWithSection.Name)) - { - symbolsByName.Add(symbolWithSection.Name, symbolWithSection); - } - } - - foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) - { - var symbolWithSection = new SymbolWithSection(null, directorySymbol); - - // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. - if (!symbolsByName.ContainsKey(symbolWithSection.Name)) - { - symbolsByName.Add(symbolWithSection.Name, symbolWithSection); - } - } - } - - /// - /// Process the complex references. - /// - /// Active section to add symbols to. - /// Sections that are referenced during the link process. - /// Collection of all components referenced by complex reference. - /// Component to feature complex references. - /// Feature to feature complex references. - /// Module to feature complex references. - private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable sections, ISet referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) - { - var componentsToModules = new Hashtable(); - - foreach (var section in sections) - { - // Need ToList since we might want to add symbols while processing. - foreach (var wixComplexReferenceRow in section.Symbols.OfType().ToList()) - { - ConnectToFeature connection; - switch (wixComplexReferenceRow.ParentType) - { - case ComplexReferenceParentType.Feature: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.Component: - connection = componentsToFeatures[wixComplexReferenceRow.Child]; - if (null == connection) - { - componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); - } - else if (wixComplexReferenceRow.IsPrimary) - { - if (connection.IsExplicitPrimaryFeature) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), connection.PrimaryFeature ?? resolvedSection.Id)); - continue; - } - else - { - connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects - connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature - connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again - } - } - else - { - connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); - } - - // add a row to the FeatureComponents table - section.AddSymbol(new FeatureComponentsSymbol - { - FeatureRef = wixComplexReferenceRow.Parent, - ComponentRef = wixComplexReferenceRow.Child, - }); - - // index the component for finding orphaned records - var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child); - referencedComponents.Add(symbolName); - - break; - - case ComplexReferenceChildType.Feature: - connection = featuresToFeatures[wixComplexReferenceRow.Child]; - if (null != connection) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); - continue; - } - - featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); - break; - - case ComplexReferenceChildType.Module: - connection = modulesToFeatures[wixComplexReferenceRow.Child]; - if (null == connection) - { - modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); - } - else if (wixComplexReferenceRow.IsPrimary) - { - if (connection.IsExplicitPrimaryFeature) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); - continue; - } - else - { - connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects - connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature - connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again - } - } - else - { - connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); - } - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - case ComplexReferenceParentType.Module: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.Component: - if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child)) - { - this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child)); - continue; - } - else - { - componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new - - // add a row to the ModuleComponents table - section.AddSymbol(new ModuleComponentsSymbol - { - Component = wixComplexReferenceRow.Child, - ModuleID = wixComplexReferenceRow.Parent, - Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage), - }); - } - - // index the component for finding orphaned records - var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child); - referencedComponents.Add(componentSymbolName); - - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - case ComplexReferenceParentType.Patch: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.PatchFamily: - case ComplexReferenceChildType.PatchFamilyGroup: - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - case ComplexReferenceParentType.Product: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.Feature: - connection = featuresToFeatures[wixComplexReferenceRow.Child]; - if (null != connection) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); - continue; - } - - featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - default: - // Note: Groups have been processed before getting here so they are not handled by any case above. - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); - } - } - } - } - - /// - /// Flattens all complex references in all sections in the collection. - /// - /// Sections that are referenced during the link process. - private void FlattenSectionsComplexReferences(IEnumerable sections) - { - var parentGroups = new Dictionary>(); - var parentGroupsSections = new Dictionary(); - var parentGroupsNeedingProcessing = new Dictionary(); - - // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); - - // Step 1: Gather all of the complex references that are going to participate - // in the flatting process. This means complex references that have "grouping - // parents" of Features, Modules, and, of course, Groups. These references - // that participate in a "grouping parent" will be removed from their section - // now and after processing added back in Step 3 below. - foreach (var section in sections) - { - var removeSymbols = new List(); - - foreach (var symbol in section.Symbols) - { - // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, - // and Module. Non-grouping complex references are simple and - // resolved during normal complex reference resolutions. - if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow && - (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)) - { - var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent); - - // Group all complex references with a common parent - // together so we can find them quickly while processing in - // Step 2. - if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs)) - { - childrenComplexRefs = new List(); - parentGroups.Add(parentTypeAndId, childrenComplexRefs); - } - - childrenComplexRefs.Add(wixComplexReferenceRow); - removeSymbols.Add(wixComplexReferenceRow); - - // Remember the mapping from set of complex references with a common - // parent to their section. We'll need this to add them back to the - // correct section in Step 3. - if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection)) - { - parentGroupsSections.Add(parentTypeAndId, section); - } - - // If the child of the complex reference is another group, then in Step 2 - // we're going to have to process this complex reference again to copy - // the child group's references into the parent group. - if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) - { - if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) - { - parentGroupsNeedingProcessing.Add(parentTypeAndId, section); - } - } - } - } - - foreach (var removeSymbol in removeSymbols) - { - section.RemoveSymbol(removeSymbol); - } - } - - Debug.Assert(parentGroups.Count == parentGroupsSections.Count); - Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); - - // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); - - // Step 2: Loop through the parent groups that have nested groups removing - // them from the hash table as they are processed. At the end of this the - // complex references should all be flattened. - var keys = parentGroupsNeedingProcessing.Keys.ToList(); - - foreach (var key in keys) - { - if (parentGroupsNeedingProcessing.ContainsKey(key)) - { - var loopDetector = new Stack(); - this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); - } - else - { - // the group must have allready been procesed and removed from the hash table - } - } - Debug.Assert(0 == parentGroupsNeedingProcessing.Count); - - // Step 3: Finally, ensure that all of the groups that were removed - // in Step 1 and flattened in Step 2 are added to their appropriate - // section. This is where we will toss out the final no-longer-needed - // groups. - foreach (var parentGroup in parentGroups.Keys) - { - var section = parentGroupsSections[parentGroup]; - - foreach (var wixComplexReferenceRow in parentGroups[parentGroup]) - { - if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && - (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && - (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) - { - section.AddSymbol(wixComplexReferenceRow); - } - } - } - - // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); - } - - private string CombineTypeAndId(ComplexReferenceParentType type, string id) - { - return String.Concat(type.ToString(), ":", id); - } - - private string CombineTypeAndId(ComplexReferenceChildType type, string id) - { - return String.Concat(type.ToString(), ":", id); - } - - /// - /// Recursively processes the group. - /// - /// String combination type and id of group to process next. - /// Stack of groups processed thus far. Used to detect loops. - /// Hash table of complex references grouped by parent id. - /// Hash table of parent groups that still have nested groups that need to be flattened. - private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Dictionary> parentGroups, Dictionary parentGroupsNeedingProcessing) - { - Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)); - loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying - - var allNewChildComplexReferences = new List(); - - var referencesToParent = parentGroups[parentTypeAndId]; - foreach (var wixComplexReferenceRow in referencesToParent) - { - Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); - Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent)); - - // We are only interested processing when the child is a group. - if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) - { - var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child); - if (loopDetector.Contains(childTypeAndId)) - { - // Create a comma delimited list of the references that participate in the - // loop for the error message. Start at the bottom of the stack and work the - // way up to present the loop as a directed graph. - var loop = String.Join(" -> ", loopDetector); - - this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop)); - - // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the - // exit of this method does at the end because we are exiting early. - loopDetector.Pop(); - parentGroupsNeedingProcessing.Remove(parentTypeAndId); - - return; // bail - } - - // Check to see if the child group still needs to be processed. If so, - // go do that so that we'll get all of that children's (and children's - // children) complex references correctly merged into our parent group. - if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) - { - this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); - } - - // If the child is a parent to anything (i.e. the parent has grandchildren) - // clone each of the children's complex references, repoint them to the parent - // complex reference (because we're moving references up the tree), and finally - // add the cloned child's complex reference to the list of complex references - // that we'll eventually add to the parent group. - if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild)) - { - foreach (var crefChild in referencesToChild) - { - // Only merge up the non-group items since groups are purged - // after this part of the processing anyway (cloning them would - // be a complete waste of time). - if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || - (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) - { - var crefChildClone = crefChild.Clone(); - Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child); - - crefChildClone.Reparent(wixComplexReferenceRow); - allNewChildComplexReferences.Add(crefChildClone); - } - } - } - } - } - - // Add the children group's complex references to the parent - // group. Clean out any left over groups and quietly remove any - // duplicate complex references that occurred during the merge. - referencesToParent.AddRange(allNewChildComplexReferences); - referencesToParent.Sort(ComplexReferenceComparision); - for (var i = referencesToParent.Count - 1; i >= 0; --i) - { - var wixComplexReferenceRow = referencesToParent[i]; - - if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) - { - referencesToParent.RemoveAt(i); - } - else if (i > 0) - { - // Since the list is already sorted, we can find duplicates by simply - // looking at the next sibling in the list and tossing out one if they - // match. - var crefCompare = referencesToParent[i - 1]; - if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) - { - referencesToParent.RemoveAt(i); - } - } - } - - int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y) - { - var comparison = x.ChildType - y.ChildType; - if (0 == comparison) - { - comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = x.ParentType - y.ParentType; - if (0 == comparison) - { - comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal); - } - } - } - } - - return comparison; - } - - loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here - parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference - } - - /* - /// - /// Debugging method for displaying the section complex references. - /// - /// The header. - /// The sections to display. - private void DisplaySectionComplexReferences(string header, SectionCollection sections) - { - Console.WriteLine(header); - foreach (Section section in sections) - { - Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; - - foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) - { - Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); - } - } - } - */ - - /// - /// Resolves the features connected to other features in the active output. - /// - /// Feature to feature complex references. - /// All symbols loaded from the sections. - private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary allSymbols) - { - foreach (ConnectToFeature connection in featuresToFeatures) - { - var wixSimpleReferenceRow = new WixSimpleReferenceSymbol - { - Table = "Feature", - PrimaryKeys = connection.ChildId - }; - - if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol)) - { - var featureSymbol = (FeatureSymbol)symbol.Symbol; - featureSymbol.ParentFeatureRef = connection.PrimaryFeature; - } - } - } - - /// - /// Resolve features for columns that have null guid placeholders. - /// - /// Symbol to resolve. - /// Number of the column containing the connection identifier. - /// Number of the column containing the feature. - /// Connect to feature complex references. - /// Hashtable of known components under multiple features. - private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) - { - var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn); - var featureId = symbol.AsString(featureColumn); - - if (EmptyGuid == featureId) - { - var connection = connectToFeatures[connectionId]; - - if (null == connection) - { - // display an error for the component or merge module as appropriate - if (null != multipleFeatureComponents) - { - this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id)); - } - else - { - this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId)); - } - } - else - { - // check for unique, implicit, primary feature parents with multiple possible parent features - if (this.ShowPedanticMessages && - !connection.IsExplicitPrimaryFeature && - 0 < connection.ConnectFeatures.Count) - { - // display a warning for the component or merge module as approrpriate - if (null != multipleFeatureComponents) - { - if (!multipleFeatureComponents.Contains(connectionId)) - { - this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId)); - - // remember this component so only one warning is generated for it - multipleFeatureComponents[connectionId] = null; - } - } - else - { - this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId)); - } - } - - // set the feature - symbol.Set(featureColumn, connection.PrimaryFeature); - } - } - } - } -} diff --git a/src/WixToolset.Core/LinkerErrors.cs b/src/WixToolset.Core/LinkerErrors.cs deleted file mode 100644 index 7ce8c00e..00000000 --- a/src/WixToolset.Core/LinkerErrors.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - - internal static class LinkerErrors - { - public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) - { - return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); - } - - public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2) - { - return Message(sourceLineNumbers, Ids.PackageInMultipleContainers, "The Package '{0}' is referenced from multiple containers - Container '{1}' and Container '{2}'. This is not currently supported.", packageId, containerId1, containerId2); - } - - public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId) - { - return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId); - } - - public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId) - { - return Message(sourceLineNumbers, Ids.UnscheduledChainPackage, "Found orphaned Package '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", packageId); - } - - public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId) - { - return Message(sourceLineNumbers, Ids.UnscheduledRollbackBoundary, "Found orphaned RollbackBoundary '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", rollbackBoundaryId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - OrphanedPayload = 7000, - PackageInMultipleContainers = 7001, - PayloadSharedWithBA = 7002, - UnscheduledChainPackage = 7003, - UnscheduledRollbackBoundary = 7004, - } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. - } -} diff --git a/src/WixToolset.Core/LinkerWarnings.cs b/src/WixToolset.Core/LinkerWarnings.cs deleted file mode 100644 index 968fa4ea..00000000 --- a/src/WixToolset.Core/LinkerWarnings.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Data; - - internal static class LinkerWarnings - { - public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) - { - return Message(sourceLineNumbers, Ids.LayoutPayloadInContainer, "The layout-only Payload '{0}' is being added to Container '{1}'. It will not be extracted during layout.", payloadId, containerId); - } - - public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2) - { - return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); - } - - public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) - { - return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - LayoutPayloadInContainer = 6900, - PayloadInMultipleContainers = 6901, - UncompressedPayloadInContainer = 6902, - } // last available is 6999. 7000 is LinkerErrors. - } -} diff --git a/src/WixToolset.Core/LocalizationParser.cs b/src/WixToolset.Core/LocalizationParser.cs deleted file mode 100644 index d6113fc6..00000000 --- a/src/WixToolset.Core/LocalizationParser.cs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Bind; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class LocalizationParser : ILocalizationParser - { - public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; - private const string XmlElementName = "WixLocalization"; - - internal LocalizationParser(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - private IMessaging Messaging { get; } - - public Localization ParseLocalization(string path) - { - var document = XDocument.Load(path); - return this.ParseLocalization(document); - } - - public Localization ParseLocalization(XDocument document) - { - var root = document.Root; - Localization localization = null; - - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(root); - if (LocalizationParser.XmlElementName == root.Name.LocalName) - { - if (LocalizationParser.WxlNamespace == root.Name.Namespace) - { - localization = ParseWixLocalizationElement(this.Messaging, root); - } - else // invalid or missing namespace - { - if (null == root.Name.Namespace) - { - this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName)); - } - else - { - this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName)); - } - } - } - else - { - this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName)); - } - - return localization; - } - - /// - /// Adds a WixVariableRow to a dictionary while performing the expected override checks. - /// - /// - /// Dictionary of variable rows. - /// Row to add to the variables dictionary. - private static void AddWixVariable(IMessaging messaging, IDictionary variables, BindVariable wixVariableRow) - { - if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) - { - variables[wixVariableRow.Id] = wixVariableRow; - } - else if (!wixVariableRow.Overridable) - { - messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); - } - } - - /// - /// Parses the WixLocalization element. - /// - /// - /// Element to parse. - private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node) - { - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - int? codepage = null; - int? summaryInformationCodepage = null; - string culture = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); - break; - case "SummaryInformationCodepage": - summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); - break; - case "Culture": - culture = attrib.Value; - break; - case "Language": - // do nothing; @Language is used for locutil which can't convert Culture to lcid - break; - default: - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - break; - } - } - else - { - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - } - } - - var variables = new Dictionary(); - var localizedControls = new Dictionary(); - - foreach (var child in node.Elements()) - { - if (LocalizationParser.WxlNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "String": - LocalizationParser.ParseString(messaging, child, variables); - break; - - case "UI": - LocalizationParser.ParseUI(messaging, child, localizedControls); - break; - - default: - messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); - break; - } - } - else - { - messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); - } - } - - return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls); - } - - /// - /// Parse a localization string into a WixVariableRow. - /// - /// - /// Element to parse. - /// - private static void ParseString(IMessaging messaging, XElement node, IDictionary variables) - { - string id = null; - var overridable = false; - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); - break; - case "Overridable": - overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - case "Localizable": - ; // do nothing - break; - default: - messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); - break; - } - } - else - { - messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); - } - } - - var value = Common.GetInnerText(node); - - if (null == id) - { - messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id")); - } - else if (0 == id.Length) - { - messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0)); - } - - if (!messaging.EncounteredError) - { - var variable = new BindVariable - { - SourceLineNumbers = sourceLineNumbers, - Id = id, - Overridable = overridable, - Value = value, - }; - - LocalizationParser.AddWixVariable(messaging, variables, variable); - } - } - - /// - /// Parse a localized control. - /// - /// - /// Element to parse. - /// Dictionary of localized controls. - private static void ParseUI(IMessaging messaging, XElement node, IDictionary localizedControls) - { - string dialog = null; - string control = null; - var x = CompilerConstants.IntegerNotSet; - var y = CompilerConstants.IntegerNotSet; - var width = CompilerConstants.IntegerNotSet; - var height = CompilerConstants.IntegerNotSet; - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - var rightToLeft = false; - var rightAligned = false; - var leftScroll = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Dialog": - dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); - break; - case "Control": - control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); - break; - case "X": - x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Y": - y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Width": - width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Height": - height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "RightToLeft": - rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - case "RightAligned": - rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - case "LeftScroll": - leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - default: - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - break; - } - } - else - { - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - } - } - - var text = Common.GetInnerText(node); - - if (String.IsNullOrEmpty(control) && (rightToLeft || rightAligned || leftScroll)) - { - if (rightToLeft) - { - messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control")); - } - - if (rightAligned) - { - messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control")); - } - - if (leftScroll) - { - messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control")); - } - } - - if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog)) - { - messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control")); - } - - if (!messaging.EncounteredError) - { - var localizedControl = new LocalizedControl(dialog, control, x, y, width, height, rightToLeft, rightAligned, leftScroll, text); - var key = localizedControl.GetKey(); - if (localizedControls.ContainsKey(key)) - { - if (String.IsNullOrEmpty(localizedControl.Control)) - { - messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog)); - } - else - { - messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control)); - } - } - else - { - localizedControls.Add(key, localizedControl); - } - } - } - } -} diff --git a/src/WixToolset.Core/ParsedWixVariable.cs b/src/WixToolset.Core/ParsedWixVariable.cs deleted file mode 100644 index 9d308b77..00000000 --- a/src/WixToolset.Core/ParsedWixVariable.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - internal class ParsedWixVariable - { - public int Index { get; set; } - - public int Length { get; set; } - - public string Namespace { get; set; } - - public string Name { get; set; } - - public string Scope { get; set; } - - public string DefaultValue { get; set; } - } -} diff --git a/src/WixToolset.Core/Preprocess/IfContext.cs b/src/WixToolset.Core/Preprocess/IfContext.cs deleted file mode 100644 index 91173c29..00000000 --- a/src/WixToolset.Core/Preprocess/IfContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - /// - /// Context for an if statement in the preprocessor. - /// - internal class IfContext - { - private bool keep; - - /// - /// Creates a default if context object, which are used for if's within an inactive preprocessor block - /// - public IfContext() - { - this.WasEverTrue = true; - this.IfState = IfState.If; - } - - /// - /// Creates an if context object. - /// - /// Flag if context is currently active. - /// Flag if context is currently true. - /// State of context to start in. - public IfContext(bool active, bool keep, IfState state) - { - this.Active = active; - this.keep = keep; - this.WasEverTrue = keep; - this.IfState = IfState.If; - } - - /// - /// Gets and sets if this if context is currently active. - /// - /// true if context is active. - public bool Active { get; set; } - - /// - /// Gets and sets if context is current true. - /// - /// true if context is currently true. - public bool IsTrue - { - get - { - return this.keep; - } - - set - { - this.keep = value; - if (this.keep) - { - this.WasEverTrue = true; - } - } - } - - /// - /// Gets if the context was ever true. - /// - /// True if context was ever true. - public bool WasEverTrue { get; private set; } - - /// - /// Gets the current state of the if context. - /// - /// Current state of context. - public IfState IfState { get; set; } - } -} diff --git a/src/WixToolset.Core/Preprocess/IfDefEventHandler.cs b/src/WixToolset.Core/Preprocess/IfDefEventHandler.cs deleted file mode 100644 index 6b56638a..00000000 --- a/src/WixToolset.Core/Preprocess/IfDefEventHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - using System; - using WixToolset.Data; - - internal delegate void IfDefEventHandler(object sender, IfDefEventArgs e); - - internal class IfDefEventArgs : EventArgs - { - public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName) - { - this.SourceLineNumbers = sourceLineNumbers; - this.IsIfDef = isIfDef; - this.IsDefined = isDefined; - this.VariableName = variableName; - } - - public SourceLineNumber SourceLineNumbers { get; } - - public bool IsDefined { get; } - - public bool IsIfDef { get; } - - public string VariableName { get; } - } -} diff --git a/src/WixToolset.Core/Preprocess/IfState.cs b/src/WixToolset.Core/Preprocess/IfState.cs deleted file mode 100644 index f5bb3e87..00000000 --- a/src/WixToolset.Core/Preprocess/IfState.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - /// - /// Current state of the if context. - /// - internal enum IfState - { - /// Context currently in unknown state. - Unknown, - - /// Context currently inside if statement. - If, - - /// Context currently inside elseif statement.. - ElseIf, - - /// Conext currently inside else statement. - Else, - } -} diff --git a/src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs b/src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs deleted file mode 100644 index 3c8ff2e8..00000000 --- a/src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - using System; - using WixToolset.Data; - - /// - /// Included file event handler delegate. - /// - /// Sender of the message. - /// Arguments for the included file event. - internal delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e); - - /// - /// Event args for included file event. - /// - internal class IncludedFileEventArgs : EventArgs - { - /// - /// Creates a new IncludedFileEventArgs. - /// - /// Source line numbers for the included file. - /// The full path of the included file. - public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName) - { - this.SourceLineNumbers = sourceLineNumbers; - this.FullName = fullName; - } - - /// - /// Gets the full path of the included file. - /// - /// The full path of the included file. - public string FullName { get; } - - /// - /// Gets the source line numbers. - /// - /// The source line numbers. - public SourceLineNumber SourceLineNumbers { get; } - } -} diff --git a/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs deleted file mode 100644 index 086a0f1a..00000000 --- a/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - /// - /// Enumeration for preprocessor operations in if statements. - /// - internal enum PreprocessorOperation - { - /// The and operator. - And, - - /// The or operator. - Or, - - /// The not operator. - Not - } -} diff --git a/src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs b/src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs deleted file mode 100644 index 672b4b9f..00000000 --- a/src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - using System; - using System.Xml.Linq; - - /// - /// Preprocessed output stream event handler delegate. - /// - /// Sender of the message. - /// Arguments for the preprocessed stream event. - internal delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e); - - /// - /// Event args for preprocessed stream event. - /// - internal class ProcessedStreamEventArgs : EventArgs - { - /// - /// Creates a new ProcessedStreamEventArgs. - /// - /// Source file that is preprocessed. - /// Preprocessed output document. - public ProcessedStreamEventArgs(string sourceFile, XDocument document) - { - this.SourceFile = sourceFile; - this.Document = document; - } - - /// - /// Gets the full path of the source file. - /// - /// The full path of the source file. - public string SourceFile { get; } - - /// - /// Gets the preprocessed output stream. - /// - /// The the preprocessed output stream. - public XDocument Document { get; } - } -} diff --git a/src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs b/src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs deleted file mode 100644 index 6d159ad0..00000000 --- a/src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core.Preprocess -{ - using System; - using WixToolset.Data; - - internal delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e); - - internal class ResolvedVariableEventArgs : EventArgs - { - public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue) - { - this.SourceLineNumbers = sourceLineNumbers; - this.VariableName = variableName; - this.VariableValue = variableValue; - } - - public SourceLineNumber SourceLineNumbers { get; } - - public string VariableName { get; } - - public string VariableValue { get; } - } -} diff --git a/src/WixToolset.Core/PreprocessContext.cs b/src/WixToolset.Core/PreprocessContext.cs deleted file mode 100644 index 986045ff..00000000 --- a/src/WixToolset.Core/PreprocessContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class PreprocessContext : IPreprocessContext - { - internal PreprocessContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection Extensions { get; set; } - - public Platform Platform { get; set; } - - public IReadOnlyCollection IncludeSearchPaths { get; set; } - - public string SourcePath { get; set; } - - public IDictionary Variables { get; set; } - - public SourceLineNumber CurrentSourceLineNumber { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/PreprocessResult.cs b/src/WixToolset.Core/PreprocessResult.cs deleted file mode 100644 index 83b29a90..00000000 --- a/src/WixToolset.Core/PreprocessResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Extensibility.Data; - - internal class PreprocessResult : IPreprocessResult - { - public XDocument Document { get; set; } - - public IReadOnlyCollection IncludedFiles { get; set; } - } -} diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs deleted file mode 100644 index 603c0e5b..00000000 --- a/src/WixToolset.Core/Preprocessor.cs +++ /dev/null @@ -1,1520 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Core.Preprocess; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Preprocessor object - /// - internal class Preprocessor : IPreprocessor - { - private static readonly Regex DefineRegex = new Regex(@"^\s*(?.+?)\s*(=\s*(?.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); - private static readonly Regex PragmaRegex = new Regex(@"^\s*(?.+?)(?[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); - - private static readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings() - { - ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, - XmlResolver = null, - }; - - private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() - { - ConformanceLevel = ConformanceLevel.Fragment, - ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, - XmlResolver = null, - }; - - internal Preprocessor(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - /// - /// Event for ifdef/ifndef directives. - /// - public event IfDefEventHandler IfDef; - - /// - /// Event for included files. - /// - public event IncludedFileEventHandler IncludedFile; - - /// - /// Event for preprocessed stream. - /// - public event ProcessedStreamEventHandler ProcessedStream; - - // - // Event for resolved variables. - // - // TOOD: Remove? - //public event ResolvedVariableEventHandler ResolvedVariable; - - /// - /// Get the source line information for the current element. The precompiler will insert - /// special source line number information for each element that it encounters. - /// - /// Element to get source line information for. - /// - /// The source line number used to author the element being processed or - /// null if the preprocessor did not process the element or the node is - /// not an element. - /// - public static SourceLineNumber GetSourceLineNumbers(XObject node) - { - return SourceLineNumber.GetFromXAnnotation(node); - } - - /// - /// Preprocesses a file. - /// - /// The preprocessing context. - /// XDocument with the postprocessed data. - public IPreprocessResult Preprocess(IPreprocessContext context) - { - var state = new ProcessingState(this.ServiceProvider, context); - - this.PreProcess(state); - - IPreprocessResult result; - using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings)) - { - result = this.Process(state, reader); - } - - this.PostProcess(state, result); - - return result; - } - - /// - /// Preprocesses a file. - /// - /// The preprocessing context. - /// XmlReader to processing the context. - /// XDocument with the postprocessed data. - public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader) - { - if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) - { - var uri = new Uri(reader.BaseURI); - context.SourcePath = uri.AbsolutePath; - } - - var state = new ProcessingState(this.ServiceProvider, context); - - this.PreProcess(state); - - var result = this.Process(state, reader); - - this.PostProcess(state, result); - - return result; - } - - /// - /// Preprocesses a file. - /// - /// The preprocessing context. - /// XmlReader to processing the context. - /// XDocument with the postprocessed data. - private IPreprocessResult Process(ProcessingState state, XmlReader reader) - { - state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR")); - - // Process the reader into the output. - IPreprocessResult result = null; - try - { - this.PreprocessReader(state, false, reader, state.Output, 0); - - // Fire event with post-processed document. - this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output)); - - if (!this.Messaging.EncounteredError) - { - result = this.ServiceProvider.GetService(); - result.Document = state.Output; - result.IncludedFiles = state.IncludedFiles; - } - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(state, reader, 0); - throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); - } - - return result; - } - - /// - /// Determins if string is an operator. - /// - /// String to check. - /// true if string is an operator. - private static bool IsOperator(string operation) - { - if (operation == null) - { - return false; - } - - operation = operation.Trim(); - if (0 == operation.Length) - { - return false; - } - - if ("=" == operation || - "!=" == operation || - "<" == operation || - "<=" == operation || - ">" == operation || - ">=" == operation || - "~=" == operation) - { - return true; - } - return false; - } - - /// - /// Determines if expression is currently inside quotes. - /// - /// Expression to evaluate. - /// Index to start searching in expression. - /// true if expression is inside in quotes. - private static bool InsideQuotes(string expression, int index) - { - if (index == -1) - { - return false; - } - - var numQuotes = 0; - var tmpIndex = 0; - while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex))) - { - numQuotes++; - tmpIndex++; - } - - // found an even number of quotes before the index, so we're not inside - if (numQuotes % 2 == 0) - { - return false; - } - - // found an odd number of quotes, so we are inside - return true; - } - - /// - /// Tests expression to see if it starts with a keyword. - /// - /// Expression to test. - /// Operation to test for. - /// true if expression starts with a keyword. - private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) - { - expression = expression.ToUpperInvariant(); - switch (operation) - { - case PreprocessorOperation.Not: - if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) - { - return true; - } - break; - case PreprocessorOperation.And: - if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) - { - return true; - } - break; - case PreprocessorOperation.Or: - if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) - { - return true; - } - break; - default: - break; - } - return false; - } - - /// - /// Processes an xml reader into an xml writer. - /// - /// - /// Specifies if reader is from an included file. - /// Reader for the source document. - /// Node where content should be added. - /// Original offset for the line numbers being processed. - private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset) - { - var currentContainer = container; - var containerStack = new Stack(); - - var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code - var ifStack = new Stack(); - - // process the reader into the writer - while (reader.Read()) - { - // update information here in case an error occurs before the next read - this.UpdateCurrentLineNumber(state, reader, offset); - - var sourceLineNumbers = state.Context.CurrentSourceLineNumber; - - // check for changes in conditional processing - if (XmlNodeType.ProcessingInstruction == reader.NodeType) - { - var ignore = false; - string name = null; - - switch (reader.LocalName) - { - case "if": - ifStack.Push(ifContext); - if (ifContext.IsTrue) - { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If); - } - else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true - { - ifContext = new IfContext(); - } - ignore = true; - break; - - case "ifdef": - ifStack.Push(ifContext); - name = reader.Value.Trim(); - if (ifContext.IsTrue) - { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); - } - else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true - { - ifContext = new IfContext(); - } - ignore = true; - this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); - break; - - case "ifndef": - ifStack.Push(ifContext); - name = reader.Value.Trim(); - if (ifContext.IsTrue) - { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); - } - else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true - { - ifContext = new IfContext(); - } - ignore = true; - this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); - break; - - case "elseif": - if (0 == ifStack.Count) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); - } - - if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); - } - - ifContext.IfState = IfState.ElseIf; // we're now in an elseif - if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test - { - ifContext.IsTrue = this.EvaluateExpression(state, reader.Value); - } - else if (ifContext.IsTrue) - { - ifContext.IsTrue = false; - } - ignore = true; - break; - - case "else": - if (0 == ifStack.Count) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); - } - - if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); - } - - ifContext.IfState = IfState.Else; // we're now in an else - ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now - ignore = true; - break; - - case "endif": - if (0 == ifStack.Count) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); - } - - ifContext = ifStack.Pop(); - ignore = true; - break; - } - - if (ignore) // ignore this node since we just handled it above - { - continue; - } - } - - if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing - { - continue; - } - - switch (reader.NodeType) - { - case XmlNodeType.XmlDeclaration: - var document = currentContainer as XDocument; - if (null != document) - { - document.Declaration = new XDeclaration(null, null, null); - while (reader.MoveToNextAttribute()) - { - switch (reader.LocalName) - { - case "version": - document.Declaration.Version = reader.Value; - break; - - case "encoding": - document.Declaration.Encoding = reader.Value; - break; - - case "standalone": - document.Declaration.Standalone = reader.Value; - break; - } - } - - } - //else - //{ - // display an error? Can this happen? - //} - break; - - case XmlNodeType.ProcessingInstruction: - switch (reader.LocalName) - { - case "define": - this.PreprocessDefine(state, reader.Value); - break; - - case "error": - this.PreprocessError(state, reader.Value); - break; - - case "warning": - this.PreprocessWarning(state, reader.Value); - break; - - case "undef": - this.PreprocessUndef(state, reader.Value); - break; - - case "include": - this.UpdateCurrentLineNumber(state, reader, offset); - this.PreprocessInclude(state, reader.Value, currentContainer); - break; - - case "foreach": - this.PreprocessForeach(state, reader, currentContainer, offset); - break; - - case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); - - case "pragma": - this.PreprocessPragma(state, reader.Value, currentContainer); - break; - - default: - // unknown processing instructions are currently ignored - break; - } - break; - - case XmlNodeType.Element: - if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek()) - { - if ("Include" != reader.LocalName) - { - this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); - } - - state.IncludeNextStack.Pop(); - state.IncludeNextStack.Push(false); - break; - } - - var empty = reader.IsEmptyElement; - var ns = XNamespace.Get(reader.NamespaceURI); - var element = new XElement(ns + reader.LocalName); - currentContainer.Add(element); - - this.UpdateCurrentLineNumber(state, reader, offset); - element.AddAnnotation(sourceLineNumbers); - - while (reader.MoveToNextAttribute()) - { - var value = state.Helper.PreprocessString(state.Context, reader.Value); - - var attribNamespace = XNamespace.Get(reader.NamespaceURI); - attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; - - element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); - } - - if (!empty) - { - containerStack.Push(currentContainer); - currentContainer = element; - } - break; - - case XmlNodeType.EndElement: - if (0 < reader.Depth || !include) - { - currentContainer = containerStack.Pop(); - } - break; - - case XmlNodeType.Text: - var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value); - currentContainer.Add(postprocessedText); - break; - - case XmlNodeType.CDATA: - var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value); - currentContainer.Add(new XCData(postprocessedValue)); - break; - - default: - break; - } - } - - if (0 != ifStack.Count) - { - throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif")); - } - - // TODO: can this actually happen? - if (0 != containerStack.Count) - { - throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes")); - } - } - - /// - /// Processes an error processing instruction. - /// - /// - /// Text from source. - private void PreprocessError(ProcessingState state, string errorMessage) - { - // Resolve other variables in the error message. - errorMessage = state.Helper.PreprocessString(state.Context, errorMessage); - - throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage)); - } - - /// - /// Processes a warning processing instruction. - /// - /// - /// Text from source. - private void PreprocessWarning(ProcessingState state, string warningMessage) - { - // Resolve other variables in the warning message. - warningMessage = state.Helper.PreprocessString(state.Context, warningMessage); - - this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage)); - } - - /// - /// Processes a define processing instruction and creates the appropriate parameter. - /// - /// - /// Text from source. - private void PreprocessDefine(ProcessingState state, string originalDefine) - { - var match = DefineRegex.Match(originalDefine); - - if (!match.Success) - { - throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine)); - } - - var defineName = match.Groups["varName"].Value; - var defineValue = match.Groups["varValue"].Value; - - // strip off the optional quotes - if (1 < defineValue.Length && - ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal)) - || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal)))) - { - defineValue = defineValue.Substring(1, defineValue.Length - 2); - } - - // resolve other variables in the variable value - defineValue = state.Helper.PreprocessString(state.Context, defineValue); - - if (defineName.StartsWith("var.", StringComparison.Ordinal)) - { - state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue); - } - else - { - state.Helper.AddVariable(state.Context, defineName, defineValue); - } - } - - /// - /// Processes an undef processing instruction and creates the appropriate parameter. - /// - /// - /// Text from source. - private void PreprocessUndef(ProcessingState state, string originalDefine) - { - var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim()); - - if (name.StartsWith("var.", StringComparison.Ordinal)) - { - state.Helper.RemoveVariable(state.Context, name.Substring(4)); - } - else - { - state.Helper.RemoveVariable(state.Context, name); - } - } - - /// - /// Processes an included file. - /// - /// - /// Path to included file. - /// Parent container for included content. - private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent) - { - var sourceLineNumbers = state.Context.CurrentSourceLineNumber; - - // Preprocess variables in the path. - includePath = state.Helper.PreprocessString(state.Context, includePath); - - var includeFile = this.GetIncludeFile(state, includePath); - - if (null == includeFile) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); - } - - using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) - { - this.PushInclude(state, includeFile); - - // process the included reader into the writer - try - { - this.PreprocessReader(state, true, reader, parent, 0); - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(state, reader, 0); - throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message)); - } - - this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); - - var includedFile = this.ServiceProvider.GetService(); - includedFile.Path = includeFile; - includedFile.SourceLineNumbers = sourceLineNumbers; - - state.IncludedFiles.Add(includedFile); - - this.PopInclude(state); - } - } - - /// - /// Preprocess a foreach processing instruction. - /// - /// - /// The xml reader. - /// The container where to output processed data. - /// Offset for the line numbers. - private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset) - { - // Find the "in" token. - var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); - if (0 > indexOfInToken) - { - throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value)); - } - - // parse out the variable name - var varName = reader.Value.Substring(0, indexOfInToken).Trim(); - var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); - - // preprocess the variable values string because it might be a variable itself - varValuesString = state.Helper.PreprocessString(state.Context, varValuesString); - - var varValues = varValuesString.Split(';'); - - // go through all the empty strings - while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType) - { - } - - // get the offset of this xml fragment (for some reason its always off by 1) - var lineInfoReader = reader as IXmlLineInfo; - if (null != lineInfoReader) - { - offset += lineInfoReader.LineNumber - 1; - } - - var textReader = reader as XmlTextReader; - // dump the xml to a string (maintaining whitespace if possible) - if (null != textReader) - { - textReader.WhitespaceHandling = WhitespaceHandling.All; - } - - var fragmentBuilder = new StringBuilder(); - var nestedForeachCount = 1; - while (nestedForeachCount != 0) - { - if (reader.NodeType == XmlNodeType.ProcessingInstruction) - { - switch (reader.LocalName) - { - case "foreach": - ++nestedForeachCount; - // Output the foreach statement - fragmentBuilder.AppendFormat("", reader.Value); - break; - - case "endforeach": - --nestedForeachCount; - if (0 != nestedForeachCount) - { - fragmentBuilder.Append(""); - } - break; - - default: - fragmentBuilder.AppendFormat("", reader.LocalName, reader.Value); - break; - } - } - else if (reader.NodeType == XmlNodeType.Element) - { - fragmentBuilder.Append(reader.ReadOuterXml()); - continue; - } - else if (reader.NodeType == XmlNodeType.Whitespace) - { - // Or output the whitespace - fragmentBuilder.Append(reader.Value); - } - else if (reader.NodeType == XmlNodeType.None) - { - throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber)); - } - - reader.Read(); - } - - using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) - { - // process each iteration, updating the variable's value each time - foreach (var varValue in varValues) - { - using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) - { - // Always overwrite foreach variables. - state.Helper.AddVariable(state.Context, varName, varValue, false); - - try - { - this.PreprocessReader(state, false, loopReader, container, offset); - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(state, loopReader, offset); - throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); - } - - fragmentStream.Position = 0; // seek back to the beginning for the next loop. - } - } - } - } - - /// - /// Processes a pragma processing instruction - /// - /// - /// Text from source. - /// - private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent) - { - var match = PragmaRegex.Match(pragmaText); - - if (!match.Success) - { - throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText)); - } - - // resolve other variables in the pragma argument(s) - var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim(); - - try - { - state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); - } - catch (Exception e) - { - throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message)); - } - } - - /// - /// Gets the next token in an expression. - /// - /// - /// Expression to parse. - /// Expression with token removed. - /// Flag if token is a string literal instead of a variable. - /// Next token. - private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral) - { - stringLiteral = false; - var token = String.Empty; - expression = expression.Trim(); - if (0 == expression.Length) - { - return String.Empty; - } - - if (expression.StartsWith("\"", StringComparison.Ordinal)) - { - stringLiteral = true; - var endingQuotes = expression.IndexOf('\"', 1); - if (-1 == endingQuotes) - { - throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // cut the quotes off the string - token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1)); - - // advance past this string - expression = expression.Substring(endingQuotes + 1).Trim(); - } - else if (expression.StartsWith("$(", StringComparison.Ordinal)) - { - // Find the ending paren of the expression - var endingParen = -1; - var openedCount = 1; - for (var i = 2; i < expression.Length; i++) - { - if ('(' == expression[i]) - { - openedCount++; - } - else if (')' == expression[i]) - { - openedCount--; - } - - if (openedCount == 0) - { - endingParen = i; - break; - } - } - - if (-1 == endingParen) - { - throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - token = expression.Substring(0, endingParen + 1); - - // Advance past this variable - expression = expression.Substring(endingParen + 1).Trim(); - } - else - { - // Cut the token off at the next equal, space, inequality operator, - // or end of string, whichever comes first - var space = expression.IndexOf(" ", StringComparison.Ordinal); - var equals = expression.IndexOf("=", StringComparison.Ordinal); - var lessThan = expression.IndexOf("<", StringComparison.Ordinal); - var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal); - var greaterThan = expression.IndexOf(">", StringComparison.Ordinal); - var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal); - var notEquals = expression.IndexOf("!=", StringComparison.Ordinal); - var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal); - int closingIndex; - - if (space == -1) - { - space = Int32.MaxValue; - } - - if (equals == -1) - { - equals = Int32.MaxValue; - } - - if (lessThan == -1) - { - lessThan = Int32.MaxValue; - } - - if (lessThanEquals == -1) - { - lessThanEquals = Int32.MaxValue; - } - - if (greaterThan == -1) - { - greaterThan = Int32.MaxValue; - } - - if (greaterThanEquals == -1) - { - greaterThanEquals = Int32.MaxValue; - } - - if (notEquals == -1) - { - notEquals = Int32.MaxValue; - } - - if (equalsNoCase == -1) - { - equalsNoCase = Int32.MaxValue; - } - - closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals))))))); - - if (Int32.MaxValue == closingIndex) - { - closingIndex = expression.Length; - } - - // If the index is 0, we hit an operator, so return it - if (0 == closingIndex) - { - // Length 2 operators - if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase) - { - closingIndex = 2; - } - else // Length 1 operators - { - closingIndex = 1; - } - } - - // Cut out the new token - token = expression.Substring(0, closingIndex).Trim(); - expression = expression.Substring(closingIndex).Trim(); - } - - return token; - } - - /// - /// Gets the value for a variable. - /// - /// - /// Original expression for error message. - /// Variable to evaluate. - /// Value of variable. - private string EvaluateVariable(ProcessingState state, string originalExpression, string variable) - { - // By default it's a literal and will only be evaluated if it - // matches the variable format - var varValue = variable; - - if (variable.StartsWith("$(", StringComparison.Ordinal)) - { - try - { - varValue = state.Helper.PreprocessString(state.Context, variable); - } - catch (ArgumentNullException) - { - // non-existent variables are expected - varValue = null; - } - } - else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) - { - // make sure it doesn't contain parenthesis - throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) - { - // shouldn't contain quotes - throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - return varValue; - } - - /// - /// Gets the left side value, operator, and right side value of an expression. - /// - /// - /// Original expression to evaluate. - /// Expression modified while processing. - /// Left side value from expression. - /// Operation in expression. - /// Right side value from expression. - private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) - { - leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral); - - // If it wasn't a string literal, evaluate it - if (!stringLiteral) - { - leftValue = this.EvaluateVariable(state, originalExpression, leftValue); - } - - // Get the operation - operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); - if (IsOperator(operation)) - { - if (stringLiteral) - { - throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); - - // If it wasn't a string literal, evaluate it - if (!stringLiteral) - { - rightValue = this.EvaluateVariable(state, originalExpression, rightValue); - } - } - else - { - // Prepend the token back on the expression since it wasn't an operator - // and put the quotes back on the literal if necessary - - if (stringLiteral) - { - operation = "\"" + operation + "\""; - } - expression = (operation + " " + expression).Trim(); - - // If no operator, just check for existence - operation = ""; - rightValue = ""; - } - } - - /// - /// Evaluates an expression. - /// - /// - /// Original expression to evaluate. - /// Expression modified while processing. - /// true if expression evaluates to true. - private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression) - { - // Quick test to see if the first token is a variable - var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); - this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue); - - var expressionValue = false; - - // If the variables don't exist, they were evaluated to null - if (null == leftValue || null == rightValue) - { - if (operation.Length > 0) - { - throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // false expression - } - else if (operation.Length == 0) - { - // There is no right side of the equation. - // If the variable was evaluated, it exists, so the expression is true - if (startsWithVariable) - { - expressionValue = true; - } - else - { - throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression)); - } - } - else - { - leftValue = leftValue.Trim(); - rightValue = rightValue.Trim(); - if ("=" == operation) - { - if (leftValue == rightValue) - { - expressionValue = true; - } - } - else if ("!=" == operation) - { - if (leftValue != rightValue) - { - expressionValue = true; - } - } - else if ("~=" == operation) - { - if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase)) - { - expressionValue = true; - } - } - else - { - // Convert the numbers from strings - int rightInt; - int leftInt; - try - { - rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture); - leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // Compare the numbers - if ("<" == operation && leftInt < rightInt || - "<=" == operation && leftInt <= rightInt || - ">" == operation && leftInt > rightInt || - ">=" == operation && leftInt >= rightInt) - { - expressionValue = true; - } - } - } - - return expressionValue; - } - - /// - /// Gets a sub-expression in parenthesis. - /// - /// - /// Original expression to evaluate. - /// Expression modified while processing. - /// Index of end of sub-expression. - /// Sub-expression in parenthesis. - private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression) - { - endSubExpression = 0; - - // if the expression doesn't start with parenthesis, leave it alone - if (!expression.StartsWith("(", StringComparison.Ordinal)) - { - return expression; - } - - // search for the end of the expression with the matching paren - var openParenIndex = 0; - var closeParenIndex = 1; - while (openParenIndex != -1 && openParenIndex < closeParenIndex) - { - closeParenIndex = expression.IndexOf(')', closeParenIndex); - if (closeParenIndex == -1) - { - throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - if (InsideQuotes(expression, closeParenIndex)) - { - // ignore stuff inside quotes (it's a string literal) - } - else - { - // Look to see if there is another open paren before the close paren - // and skip over the open parens while they are in a string literal - do - { - openParenIndex++; - openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex); - } - while (InsideQuotes(expression, openParenIndex)); - } - - // Advance past the closing paren - closeParenIndex++; - } - - endSubExpression = closeParenIndex; - - // Return the expression minus the parenthesis - return expression.Substring(1, closeParenIndex - 2); - } - - /// - /// Updates expression based on operation. - /// - /// - /// State to update. - /// Operation to apply to current value. - /// Previous result. - private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult) - { - switch (operation) - { - case PreprocessorOperation.And: - currentValue = currentValue && prevResult; - break; - case PreprocessorOperation.Or: - currentValue = currentValue || prevResult; - break; - case PreprocessorOperation.Not: - currentValue = !currentValue; - break; - default: - throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString())); - } - } - - /// - /// Evaluate an expression. - /// - /// - /// Expression to evaluate. - /// Boolean result of expression. - private bool EvaluateExpression(ProcessingState state, string expression) - { - var tmpExpression = expression; - return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true); - } - - /// - /// Recurse through the expression to evaluate if it is true or false. - /// The expression is evaluated left to right. - /// The expression is case-sensitive (converted to upper case) with the - /// following exceptions: variable names and keywords (and, not, or). - /// Comparisons with = and != are string comparisons. - /// Comparisons with inequality operators must be done on valid integers. - /// - /// The operator precedence is: - /// "" - /// () - /// <, >, <=, >=, =, != - /// Not - /// And, Or - /// - /// Valid expressions include: - /// not $(var.B) or not $(var.C) - /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A)) - /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C) - /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir) - /// $(var.A) and $(var.B)>2 or $(var.B) <= 2 - /// $(var.A) != "2" - /// - /// - /// The original expression - /// The expression currently being evaluated - /// The operation to apply to this result - /// The previous result to apply to this result - /// Boolean to indicate if the expression is true or false - private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) - { - var expressionValue = false; - expression = expression.Trim(); - if (expression.Length == 0) - { - throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // If the expression starts with parenthesis, evaluate it - if (expression.IndexOf('(') == 0) - { - var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex); - expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true); - - // Now get the rest of the expression that hasn't been evaluated - expression = expression.Substring(endSubExpressionIndex).Trim(); - } - else - { - // Check for NOT - if (StartsWithKeyword(expression, PreprocessorOperation.Not)) - { - expression = expression.Substring(3).Trim(); - if (expression.Length == 0) - { - throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true); - } - else // Expect a literal - { - expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression); - - // Expect the literal that was just evaluated to already be cut off - } - } - this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult); - - // If there's still an expression left, it must start with AND or OR. - if (expression.Trim().Length > 0) - { - if (StartsWithKeyword(expression, PreprocessorOperation.And)) - { - expression = expression.Substring(3); - return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue); - } - else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) - { - expression = expression.Substring(2); - return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); - } - else - { - throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression)); - } - } - - return expressionValue; - } - - /// - /// Update the current line number with the reader's current state. - /// - /// - /// The xml reader for the preprocessor. - /// This is the artificial offset of the line numbers from the reader. Used for the foreach processing. - private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset) - { - var lineInfoReader = reader as IXmlLineInfo; - if (null != lineInfoReader) - { - var newLine = lineInfoReader.LineNumber + offset; - - if (state.Context.CurrentSourceLineNumber.LineNumber != newLine) - { - state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine); - } - } - } - - /// - /// Pushes a file name on the stack of included files. - /// - /// - /// Name to push on to the stack of included files. - private void PushInclude(ProcessingState state, string fileName) - { - if (1023 < state.CurrentFileStack.Count) - { - throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count)); - } - - var path = Path.GetFullPath(fileName); - - state.CurrentFileStack.Push(path); - state.SourceStack.Push(state.Context.CurrentSourceLineNumber); - state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber); - state.IncludeNextStack.Push(true); - } - - /// - /// Pops a file name from the stack of included files. - /// - private void PopInclude(ProcessingState state) - { - state.Context.CurrentSourceLineNumber = state.SourceStack.Pop(); - - state.CurrentFileStack.Pop(); - state.IncludeNextStack.Pop(); - } - - /// - /// Go through search paths, looking for a matching include file. - /// Start the search in the directory of the source file, then go - /// through the search paths in the order given on the command line - /// (leftmost first, ...). - /// - /// - /// User-specified path to the included file (usually just the file name). - /// Returns a FileInfo for the found include file, or null if the file cannot be found. - private string GetIncludeFile(ProcessingState state, string includePath) - { - string finalIncludePath = null; - - includePath = includePath.Trim(); - - // remove quotes (only if they match) - if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) || - (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal))) - { - includePath = includePath.Substring(1, includePath.Length - 2); - } - - // check if the include file is a full path - if (Path.IsPathRooted(includePath)) - { - if (File.Exists(includePath)) - { - finalIncludePath = includePath; - } - } - else // relative path - { - // build a string to test the directory containing the source file first - var currentFolder = state.CurrentFileStack.Peek(); - var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath); - - // test the source file directory - if (File.Exists(includeTestPath)) - { - finalIncludePath = includeTestPath; - } - else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line - { - foreach (var includeSearchPath in state.Context.IncludeSearchPaths) - { - // if the path exists, we have found the final string - includeTestPath = Path.Combine(includeSearchPath, includePath); - if (File.Exists(includeTestPath)) - { - finalIncludePath = includeTestPath; - break; - } - } - } - } - - return finalIncludePath; - } - - private void PreProcess(ProcessingState state) - { - if (state.Context.Extensions == null) - { - return; - } - - foreach (var extension in state.Context.Extensions) - { - if (extension.Prefixes != null) - { - foreach (var prefix in extension.Prefixes) - { - if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) - { - state.ExtensionsByPrefix.Add(prefix, extension); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); - } - } - } - - extension.PrePreprocess(state.Context); - } - } - - private void PostProcess(ProcessingState state, IPreprocessResult result) - { - if (state.Context.Extensions == null) - { - return; - } - - foreach (var extension in state.Context.Extensions) - { - extension.PostPreprocess(result); - } - } - - private class ProcessingState - { - public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context) - { - var path = Path.GetFullPath(context.SourcePath); - - this.Context = context; - this.Context.CurrentSourceLineNumber = new SourceLineNumber(path); - this.Context.Variables = this.Context.Variables == null ? new Dictionary() : new Dictionary(this.Context.Variables); - - this.Helper = serviceProvider.GetService(); - } - - public IPreprocessContext Context { get; } - - public IPreprocessHelper Helper { get; } - - public List IncludedFiles { get; } = new List(); - - public XDocument Output { get; } = new XDocument(); - - public Stack CurrentFileStack { get; } = new Stack(); - - public Dictionary ExtensionsByPrefix { get; } = new Dictionary(); - - public Stack IncludeNextStack { get; } = new Stack(); - - public Stack SourceStack { get; } = new Stack(); - } - } -} diff --git a/src/WixToolset.Core/Properties/AssemblyInfo.cs b/src/WixToolset.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 81274e3f..00000000 --- a/src/WixToolset.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +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. - -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyCulture("")] -[assembly: CLSCompliant(false)] -[assembly: ComVisible(false)] diff --git a/src/WixToolset.Core/ResolveContext.cs b/src/WixToolset.Core/ResolveContext.cs deleted file mode 100644 index 638c8079..00000000 --- a/src/WixToolset.Core/ResolveContext.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ResolveContext : IResolveContext - { - internal ResolveContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection BindPaths { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection ExtensionData { get; set; } - - public IReadOnlyCollection FilterCultures { get; set; } - - public string IntermediateFolder { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - - public IReadOnlyCollection Localizations { get; set; } - - public IVariableResolver VariableResolver { get; set; } - - public bool AllowUnresolvedVariables { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/ResolveFileResult.cs b/src/WixToolset.Core/ResolveFileResult.cs deleted file mode 100644 index f6e201d4..00000000 --- a/src/WixToolset.Core/ResolveFileResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Collections.Generic; - using WixToolset.Extensibility.Data; - - internal class ResolveFileResult : IResolveFileResult - { - public string Path { get; set; } - - public IReadOnlyCollection CheckedPaths { get; set; } - } -} diff --git a/src/WixToolset.Core/ResolveResult.cs b/src/WixToolset.Core/ResolveResult.cs deleted file mode 100644 index fa8e09b7..00000000 --- a/src/WixToolset.Core/ResolveResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class ResolveResult : IResolveResult - { - public int? Codepage { get; set; } - - public int? SummaryInformationCodepage { get; set; } - - public int? PackageLcid { get; set; } - - public IReadOnlyCollection DelayedFields { get; set; } - - public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - } -} diff --git a/src/WixToolset.Core/ResolvedCabinet.cs b/src/WixToolset.Core/ResolvedCabinet.cs deleted file mode 100644 index be04831f..00000000 --- a/src/WixToolset.Core/ResolvedCabinet.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Data; - - /// - /// Data returned from build file manager ResolveCabinet callback. - /// - internal class ResolvedCabinet : IResolvedCabinet - { - /// - /// Gets or sets the build option for the resolved cabinet. - /// - public CabinetBuildOption BuildOption { get; set; } - - /// - /// Gets or sets the path for the resolved cabinet. - /// - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core/Resolver.cs b/src/WixToolset.Core/Resolver.cs deleted file mode 100644 index e93f8e1b..00000000 --- a/src/WixToolset.Core/Resolver.cs +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Core.Bind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Resolver for the WiX toolset. - /// - internal class Resolver : IResolver - { - internal Resolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = serviceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - public IResolveResult Resolve(IResolveContext context) - { - foreach (var extension in context.Extensions) - { - extension.PreResolve(context); - } - - ResolveResult resolveResult = null; - try - { - var filteredLocalizations = FilterLocalizations(context); - - var variableResolver = this.CreateVariableResolver(context, filteredLocalizations); - - this.LocalizeUI(variableResolver, context.IntermediateRepresentation); - - resolveResult = this.DoResolve(context, variableResolver); - - var primaryLocalization = filteredLocalizations.FirstOrDefault(); - - if (primaryLocalization != null) - { - this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo); - - resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; - - resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; - - resolveResult.PackageLcid = cultureInfo?.LCID; - } - } - finally - { - foreach (var extension in context.Extensions) - { - extension.PostResolve(resolveResult); - } - } - - return resolveResult; - } - - private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) - { - var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); - - var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); - - IReadOnlyCollection delayedFields; - { - var command = new ResolveFieldsCommand(); - command.Messaging = this.Messaging; - command.BuildingPatch = buildingPatch; - command.VariableResolver = variableResolver; - command.BindPaths = context.BindPaths; - command.Extensions = context.Extensions; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.IntermediateFolder = context.IntermediateFolder; - command.Intermediate = context.IntermediateRepresentation; - command.SupportDelayedResolution = true; - command.AllowUnresolvedVariables = context.AllowUnresolvedVariables; - command.Execute(); - - delayedFields = command.DelayedFields; - } - -#if TODO_PATCHING - if (context.IntermediateRepresentation.SubStorages != null) - { - foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) - { - var command = new ResolveFieldsCommand(); - command.BuildingPatch = buildingPatch; - command.BindVariableResolver = context.WixVariableResolver; - command.BindPaths = context.BindPaths; - command.Extensions = context.Extensions; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.IntermediateFolder = context.IntermediateFolder; - command.Intermediate = context.IntermediateRepresentation; - command.SupportDelayedResolution = false; - command.Execute(); - } - } -#endif - - var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); - - context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); - - return new ResolveResult - { - ExpectedEmbeddedFiles = expectedEmbeddedFiles, - DelayedFields = delayedFields, - IntermediateRepresentation = context.IntermediateRepresentation - }; - } - - /// - /// Localize dialogs and controls. - /// - private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate) - { - foreach (var section in intermediate.Sections) - { - foreach (var symbol in section.Symbols.OfType()) - { - if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) - { - if (CompilerConstants.IntegerNotSet != localizedControl.X) - { - symbol.HCentering = localizedControl.X; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Y) - { - symbol.VCentering = localizedControl.Y; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Width) - { - symbol.Width = localizedControl.Width; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Height) - { - symbol.Height = localizedControl.Height; - } - - symbol.RightAligned |= localizedControl.RightAligned; - symbol.RightToLeft |= localizedControl.RightToLeft; - symbol.LeftScroll |= localizedControl.LeftScroll; - - if (!String.IsNullOrEmpty(localizedControl.Text)) - { - symbol.Title = localizedControl.Text; - } - } - } - - foreach (var symbol in section.Symbols.OfType()) - { - if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) - { - if (CompilerConstants.IntegerNotSet != localizedControl.X) - { - symbol.X = localizedControl.X; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Y) - { - symbol.Y = localizedControl.Y; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Width) - { - symbol.Width = localizedControl.Width; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Height) - { - symbol.Height = localizedControl.Height; - } - - symbol.RightAligned |= localizedControl.RightAligned; - symbol.RightToLeft |= localizedControl.RightToLeft; - symbol.LeftScroll |= localizedControl.LeftScroll; - - if (!String.IsNullOrEmpty(localizedControl.Text)) - { - symbol.Text = localizedControl.Text; - } - } - } - } - } - - private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable filteredLocalizations) - { - var variableResolver = this.ServiceProvider.GetService(); - - foreach (var localization in filteredLocalizations) - { - variableResolver.AddLocalization(localization); - } - - // Gather all the wix variables. - var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType(); - foreach (var symbol in wixVariableSymbols) - { - variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); - } - - return variableResolver; - } - - private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo) - { - cultureInfo = null; - - if (!String.IsNullOrEmpty(culture)) - { - try - { - cultureInfo = new CultureInfo(culture, useUserOverride: false); - } - catch - { - this.Messaging.Write(""); - } - } - - return cultureInfo != null; - } - - private static IEnumerable FilterLocalizations(IResolveContext context) - { - var result = new List(); - var filter = CalculateCultureFilter(context); - - var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList(); - - AddFilteredLocalizations(result, filter, localizations); - - // Filter localizations provided by extensions with data. - var creator = context.ServiceProvider.GetService(); - - foreach (var data in context.ExtensionData) - { - var library = data.GetLibrary(creator); - - if (library?.Localizations != null && library.Localizations.Any()) - { - var extensionFilter = (!filter.Any() && data.DefaultCulture != null) ? new[] { data.DefaultCulture } : filter; - - AddFilteredLocalizations(result, extensionFilter, library.Localizations); - } - } - - return result; - } - - private static IEnumerable CalculateCultureFilter(IResolveContext context) - { - var filter = context.FilterCultures ?? Array.Empty(); - - // If no filter was specified, look for a language neutral localization file specified - // from the command-line (not embedded in the intermediate). If found, filter on language - // neutral. - if (!filter.Any() && context.Localizations.Any(l => String.IsNullOrEmpty(l.Culture))) - { - filter = new[] { String.Empty }; - } - - return filter; - } - - private static void AddFilteredLocalizations(List result, IEnumerable filter, IEnumerable localizations) - { - // If there is no filter, return all localizations. - if (!filter.Any()) - { - result.AddRange(localizations); - } - else // filter localizations in order specified by the filter - { - foreach (var culture in filter) - { - result.AddRange(localizations.Where(l => culture.Equals(l.Culture, StringComparison.OrdinalIgnoreCase) || String.IsNullOrEmpty(l.Culture))); - } - } - } - } -} diff --git a/src/WixToolset.Core/SourceFile.cs b/src/WixToolset.Core/SourceFile.cs deleted file mode 100644 index d7ea7a50..00000000 --- a/src/WixToolset.Core/SourceFile.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - internal class SourceFile - { - public SourceFile(string sourcePath, string outputPath) - { - this.SourcePath = sourcePath; - this.OutputPath = outputPath; - } - - public string OutputPath { get; } - - public string SourcePath { get; } - } -} diff --git a/src/WixToolset.Core/UnbindContext.cs b/src/WixToolset.Core/UnbindContext.cs deleted file mode 100644 index c3817a08..00000000 --- a/src/WixToolset.Core/UnbindContext.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using WixToolset.Extensibility.Data; - - internal class UnbindContext : IUnbindContext - { - internal UnbindContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - 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 deleted file mode 100644 index 3ef77083..00000000 --- a/src/WixToolset.Core/Unbinder.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Unbinder core of the WiX toolset. - /// - internal sealed class Unbinder : IUnbinder - { - public Unbinder(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - var extensionManager = this.ServiceProvider.GetService(); - this.BackendFactories = extensionManager.GetServices(); - } - - public IServiceProvider ServiceProvider { get; } - - 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; set; } - - /// - /// Gets or sets the option to suppress demodularizing values. - /// - /// The option to suppress demodularizing values. - public bool SuppressDemodularization { get; set; } - - /// - /// Gets or sets the option to suppress extracting cabinets. - /// - /// The option to suppress extracting cabinets. - 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 => Path.GetTempPath(); - - /// - /// Unbind a Windows Installer file. - /// - /// The Windows Installer file. - /// The type of output to create. - /// The path where files should be exported. - /// The output representing the database. - public Intermediate Unbind(string file, OutputType outputType, string exportBasePath) - { - if (!File.Exists(file)) - { - if (OutputType.Transform == outputType) - { - throw new WixException(ErrorMessages.FileNotFound(null, file, "Transform")); - } - else - { - throw new WixException(ErrorMessages.FileNotFound(null, file, "Database")); - } - } - - // if we don't have the temporary files object yet, get one - Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there - - var context = new UnbindContext(this.ServiceProvider); - context.InputFilePath = file; - context.ExportBasePath = exportBasePath; - context.IntermediateFolder = this.TempFilesLocation; - context.IsAdminImage = this.IsAdminImage; - context.SuppressDemodularization = this.SuppressDemodularization; - context.SuppressExtractCabinets = this.SuppressExtractCabinets; - - foreach (var factory in this.BackendFactories) - { - if (factory.TryCreateBackend(outputType.ToString(), file, out var backend)) - { - return backend.Unbind(context); - } - } - - // TODO: Display message that could not find a unbinder for output type? - - return null; - } - } -} diff --git a/src/WixToolset.Core/VariableResolution.cs b/src/WixToolset.Core/VariableResolution.cs deleted file mode 100644 index 3b34e294..00000000 --- a/src/WixToolset.Core/VariableResolution.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Services; - - internal class VariableResolution : IVariableResolution - { - /// - /// Indicates whether the variable should be delay resolved. - /// - public bool DelayedResolve { get; set; } - - /// - /// Indicates whether the value is the default value of the variable. - /// - public bool IsDefault { get; set; } - - /// - /// Indicates whether the value changed. - /// - public bool UpdatedValue { get; set; } - - /// - /// Resolved value. - /// - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/src/WixToolset.Core/VariableResolver.cs b/src/WixToolset.Core/VariableResolver.cs deleted file mode 100644 index 437cabb7..00000000 --- a/src/WixToolset.Core/VariableResolver.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Bind; - using WixToolset.Extensibility.Services; - - /// - /// WiX variable resolver. - /// - internal class VariableResolver : IVariableResolver - { - private readonly Dictionary locVariables; - private readonly Dictionary wixVariables; - private readonly Dictionary localizedControls; - - /// - /// Instantiate a new VariableResolver. - /// - internal VariableResolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - - this.locVariables = new Dictionary(); - this.wixVariables = new Dictionary(); - this.localizedControls = new Dictionary(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - public int VariableCount => this.wixVariables.Count; - - public void AddLocalization(Localization localization) - { - foreach (var variable in localization.Variables) - { - if (!TryAddWixVariable(this.locVariables, variable)) - { - this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id)); - } - } - - foreach (KeyValuePair localizedControl in localization.LocalizedControls) - { - if (!this.localizedControls.ContainsKey(localizedControl.Key)) - { - this.localizedControls.Add(localizedControl.Key, localizedControl.Value); - } - } - } - - public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable) - { - var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber }; - - if (!TryAddWixVariable(this.wixVariables, bindVariable)) - { - this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name)); - } - } - - public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value) - { - return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true); - } - - public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) - { - var key = LocalizedControl.GetKey(dialog, control); - return this.localizedControls.TryGetValue(key, out localizedControl); - } - - public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) - { - var start = 0; - var defaulted = true; - var delayed = false; - var updated = false; - - while (Common.TryParseWixVariable(value, start, out var parsed)) - { - var variableNamespace = parsed.Namespace; - var variableId = parsed.Name; - var variableDefaultValue = parsed.DefaultValue; - - // check for an escape sequence of !! indicating the match is not a variable expression - if (0 < parsed.Index && '!' == value[parsed.Index - 1]) - { - var sb = new StringBuilder(value); - sb.Remove(parsed.Index - 1, 1); - value = sb.ToString(); - - updated = true; - start = parsed.Index + parsed.Length - 1; - - continue; - } - - string resolvedValue = null; - - if ("loc" == variableNamespace) - { - // localization variables do not support inline default values - if (variableDefaultValue != null) - { - this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); - continue; - } - - if (this.locVariables.TryGetValue(variableId, out var bindVariable)) - { - resolvedValue = bindVariable.Value; - } - } - else if ("wix" == variableNamespace) - { - if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) - { - resolvedValue = bindVariable.Value ?? String.Empty; - defaulted = false; - } - else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified - { - resolvedValue = variableDefaultValue; - } - } - - if ("bind" == variableNamespace) - { - // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort. - delayed = true; - start = parsed.Index + parsed.Length - 1; - } - else - { - // insert the resolved value if it was found or display an error - if (null != resolvedValue) - { - if (parsed.Index == 0 && parsed.Length == value.Length) - { - value = resolvedValue; - } - else - { - var sb = new StringBuilder(value); - sb.Remove(parsed.Index, parsed.Length); - sb.Insert(parsed.Index, resolvedValue); - value = sb.ToString(); - } - - updated = true; - start = parsed.Index; - } - else - { - if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable - { - this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); - } - else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable - { - this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); - } - - start = parsed.Index + parsed.Length; - } - } - } - - return new VariableResolution - { - DelayedResolve = delayed, - IsDefault = defaulted, - UpdatedValue = updated, - Value = value, - }; - } - - private static bool TryAddWixVariable(IDictionary variables, BindVariable variable) - { - if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable)) - { - variables[variable.Id] = variable; - return true; - } - - return variable.Overridable; - } - } -} diff --git a/src/WixToolset.Core/WixToolset.Core.csproj b/src/WixToolset.Core/WixToolset.Core.csproj deleted file mode 100644 index 7242d500..00000000 --- a/src/WixToolset.Core/WixToolset.Core.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Core - WiX Toolset Core - embedded - true - true - true - - - - - <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject b/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject deleted file mode 100644 index c6001ebe..00000000 --- a/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject +++ /dev/null @@ -1,7 +0,0 @@ - - - - ..\..\version.json - - - \ No newline at end of file diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs deleted file mode 100644 index 5d700ba0..00000000 --- a/src/WixToolset.Core/WixToolsetServiceProvider.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using System; - using System.Collections.Generic; - using WixToolset.Core.CommandLine; - using WixToolset.Core.ExtensibilityServices; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class WixToolsetServiceProvider : IWixToolsetCoreServiceProvider - { - public WixToolsetServiceProvider() - { - this.CreationFunctions = new Dictionary, object>>(); - this.Singletons = new Dictionary(); - - // Singletons. - this.AddService((provider, singletons) => AddSingleton(singletons, new ExtensionManager(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new Messaging())); - this.AddService((provider, singletons) => AddSingleton(singletons, new SymbolDefinitionCreator(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new ParseHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new PreprocessHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); - this.AddService((provider, singletons) => AddSingleton(singletons, new WixBranding())); - - // Transients. - this.AddService((provider, singletons) => new CommandLineArguments(provider)); - this.AddService((provider, singletons) => new CommandLineContext(provider)); - this.AddService((provider, singletons) => new CommandLine.CommandLine(provider)); - this.AddService((provider, singletons) => new PreprocessContext(provider)); - this.AddService((provider, singletons) => new CompileContext(provider)); - this.AddService((provider, singletons) => new LibraryContext(provider)); - this.AddService((provider, singletons) => new LinkContext(provider)); - this.AddService((provider, singletons) => new ResolveContext(provider)); - this.AddService((provider, singletons) => new BindContext(provider)); - this.AddService((provider, singletons) => new DecompileContext(provider)); - this.AddService((provider, singletons) => new LayoutContext(provider)); - this.AddService((provider, singletons) => new InscribeContext(provider)); - this.AddService((provider, singletons) => new UnbindContext(provider)); - - this.AddService((provider, singletons) => new BindFileWithPath()); - this.AddService((provider, singletons) => new BindPath()); - this.AddService((provider, singletons) => new BindResult()); - this.AddService((provider, singletons) => new ComponentKeyPath()); - this.AddService((provider, singletons) => new DecompileResult()); - this.AddService((provider, singletons) => new IncludedFile()); - this.AddService((provider, singletons) => new PreprocessResult()); - this.AddService((provider, singletons) => new ResolvedDirectory()); - this.AddService((provider, singletons) => new ResolveFileResult()); - this.AddService((provider, singletons) => new ResolveResult()); - this.AddService((provider, singletons) => new ResolvedCabinet()); - this.AddService((provider, singletons) => new VariableResolution()); - - this.AddService((provider, singletons) => new Binder(provider)); - this.AddService((provider, singletons) => new Compiler(provider)); - this.AddService((provider, singletons) => new Decompiler(provider)); - this.AddService((provider, singletons) => new LayoutCreator(provider)); - this.AddService((provider, singletons) => new Preprocessor(provider)); - this.AddService((provider, singletons) => new Librarian(provider)); - this.AddService((provider, singletons) => new Linker(provider)); - this.AddService((provider, singletons) => new Resolver(provider)); - this.AddService((provider, singletons) => new Unbinder(provider)); - - this.AddService((provider, singletons) => new LocalizationParser(provider)); - this.AddService((provider, singletons) => new VariableResolver(provider)); - } - - private Dictionary, object>> CreationFunctions { get; } - - private Dictionary Singletons { get; } - - public object GetService(Type serviceType) - { - if (serviceType == null) - { - throw new ArgumentNullException(nameof(serviceType)); - } - - if (!this.Singletons.TryGetValue(serviceType, out var service)) - { - if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction)) - { - service = creationFunction(this, this.Singletons); - -#if DEBUG - if (!serviceType.IsAssignableFrom(service?.GetType())) - { - throw new InvalidOperationException($"Creation function for service type: {serviceType.Name} created incompatible service with type: {service?.GetType()}"); - } -#endif - } - } - - return service; - } - - public void AddService(Type serviceType, Func, object> creationFunction) - { - this.CreationFunctions[serviceType] = creationFunction; - } - - public void AddService(Func, T> creationFunction) where T : class - { - this.AddService(typeof(T), creationFunction); - } - - private static T AddSingleton(Dictionary singletons, T service) where T : class - { - singletons.Add(typeof(T), service); - return service; - } - } -} diff --git a/src/WixToolset.Core/WixToolsetServiceProviderFactory.cs b/src/WixToolset.Core/WixToolsetServiceProviderFactory.cs deleted file mode 100644 index 8e07070b..00000000 --- a/src/WixToolset.Core/WixToolsetServiceProviderFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. - -namespace WixToolset.Core -{ - using WixToolset.Extensibility.Services; - - /// - /// Class for creating . - /// - public static class WixToolsetServiceProviderFactory - { - /// - /// Creates a new . - /// - /// The created - public static IWixToolsetCoreServiceProvider CreateServiceProvider() - { - return new WixToolsetServiceProvider(); - } - } -} diff --git a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj deleted file mode 100644 index 88210bd4..00000000 --- a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - netcoreapp3.1 - false - Exe - - - - - - - - - $(BaseOutputPath)TestData\$(Configuration)\example.wixlib - - - - - - - - - - diff --git a/src/test/CompileCoreTestExtensionWixlib/Program.cs b/src/test/CompileCoreTestExtensionWixlib/Program.cs deleted file mode 100644 index 323b5e5e..00000000 --- a/src/test/CompileCoreTestExtensionWixlib/Program.cs +++ /dev/null @@ -1,37 +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. - -using System.Collections.Generic; -using WixToolset.Core.TestPackage; - -namespace CompileCoreTestExtensionWixlib -{ - // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools. - // So we have this helper exe. - public class Program - { - public static void Main(string[] args) - { - var intermediateFolder = args[0]; - var wixlibPath = args[1]; - - var buildArgs = new List(); - buildArgs.Add("build"); - buildArgs.Add("-bindfiles"); - buildArgs.Add("-bindpath"); - buildArgs.Add("Data"); - buildArgs.Add("-intermediateFolder"); - buildArgs.Add(intermediateFolder); - buildArgs.Add("-o"); - buildArgs.Add(wixlibPath); - - foreach (var path in args[2].Split(';')) - { - buildArgs.Add(path); - } - - var result = WixRunner.Execute(buildArgs.ToArray()); - - result.AssertSuccess(); - } - } -} diff --git a/src/test/Example.Extension/Data/example.txt b/src/test/Example.Extension/Data/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/Example.Extension/Data/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs deleted file mode 100644 index af5d5086..00000000 --- a/src/test/Example.Extension/Data/example.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj deleted file mode 100644 index 9be10d35..00000000 --- a/src/test/Example.Extension/Example.Extension.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - netcoreapp3.1 - false - embedded - - - - - - - - - - - - - - - - diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs deleted file mode 100644 index 5b8d4b3f..00000000 --- a/src/test/Example.Extension/ExampleCompilerExtension.cs +++ /dev/null @@ -1,195 +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 Example.Extension -{ - using System; - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExampleCompilerExtension : BaseCompilerExtension - { - public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; - public string BundleExtensionId => "ExampleBundleExtension"; - - public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) - { - var processed = false; - - switch (parentElement.Name.LocalName) - { - case "Bundle": - case "Fragment": - switch (element.Name.LocalName) - { - case "ExampleEnsureTable": - this.ParseExampleEnsureTableElement(intermediate, section, element); - processed = true; - break; - case "ExampleSearch": - this.ParseExampleSearchElement(intermediate, section, element); - processed = true; - break; - case "ExampleSearchRef": - this.ParseExampleSearchRefElement(intermediate, section, element); - processed = true; - break; - } - break; - case "Component": - switch (element.Name.LocalName) - { - case "Example": - this.ParseExampleElement(intermediate, section, element); - processed = true; - break; - } - break; - } - - if (!processed) - { - base.ParseElement(intermediate, section, parentElement, element, context); - } - } - - private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - Identifier id = null; - string value = null; - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - - case "Value": - value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseAttribute(intermediate, section, element, attrib, null); - } - } - - if (null == id) - { - //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - var symbol = this.ParseHelper.CreateSymbol(section, sourceLineNumbers, "Example", id); - symbol.Set(0, value); - } - } - - private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll); - } - - private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - Identifier id = null; - string searchFor = null; - string variable = null; - string condition = null; - string after = null; - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Variable": - variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SearchFor": - searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseAttribute(intermediate, section, element, attrib, null); - } - } - - if (null == id) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - this.ParseHelper.CreateWixSearchSymbol(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); - } - - if (!this.Messaging.EncounteredError) - { - var symbol = section.AddSymbol(new ExampleSearchSymbol(sourceLineNumbers, id) - { - SearchFor = searchFor, - }); - } - } - - private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, ExampleSymbolDefinitions.ExampleSearch, refId); - break; - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); - } - } - - this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); - } - } -} diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs deleted file mode 100644 index 91d60eb9..00000000 --- a/src/test/Example.Extension/ExampleExtensionData.cs +++ /dev/null @@ -1,23 +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 Example.Extension -{ - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExampleExtensionData : IExtensionData - { - public string DefaultCulture => null; - - public Intermediate GetLibrary(ISymbolDefinitionCreator symbolDefinitions) - { - return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", symbolDefinitions); - } - - public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) - { - symbolDefinition = ExampleSymbolDefinitions.ByName(name); - return symbolDefinition != null; - } - } -} \ No newline at end of file diff --git a/src/test/Example.Extension/ExampleExtensionFactory.cs b/src/test/Example.Extension/ExampleExtensionFactory.cs deleted file mode 100644 index e54561ee..00000000 --- a/src/test/Example.Extension/ExampleExtensionFactory.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 Example.Extension -{ - using System; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - public class ExampleExtensionFactory : IExtensionFactory - { - private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; - - public ExampleExtensionFactory(IWixToolsetCoreServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - /// - /// This exists just to show it is possible to get a service provider to the extension factory. - /// - private IWixToolsetCoreServiceProvider ServiceProvider { get; } - - public bool TryCreateExtension(Type extensionType, out object extension) - { - if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) - { - if (this.preprocessorExtension == null) - { - this.preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); - } - - extension = this.preprocessorExtension; - } - else if (extensionType == typeof(ICompilerExtension)) - { - extension = new ExampleCompilerExtension(); - } - else if (extensionType == typeof(IExtensionData)) - { - extension = new ExampleExtensionData(); - } - else if (extensionType == typeof(IWindowsInstallerBackendBinderExtension)) - { - extension = new ExampleWindowsInstallerBackendExtension(); - } - else - { - extension = null; - } - - return extension != null; - } - } -} diff --git a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs deleted file mode 100644 index 7244798a..00000000 --- a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs +++ /dev/null @@ -1,57 +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 Example.Extension -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine - { - private string exampleValueFromCommandLine; - - public IReadOnlyCollection CommandLineSwitches => throw new NotImplementedException(); - - public ExamplePreprocessorExtensionAndCommandLine() - { - this.Prefixes = new[] { "ex" }; - } - - public void PreParse(ICommandLineContext context) - { - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) - { - this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument); - return true; - } - - return false; - } - - public bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) - { - command = null; - return false; - } - - public void PostParse() - { - } - - public override string GetVariableValue(string prefix, string name) - { - if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) - { - return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; - } - - return null; - } - } -} diff --git a/src/test/Example.Extension/ExampleRow.cs b/src/test/Example.Extension/ExampleRow.cs deleted file mode 100644 index fc20c6c9..00000000 --- a/src/test/Example.Extension/ExampleRow.cs +++ /dev/null @@ -1,32 +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 Example.Extension -{ - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - - public class ExampleRow : Row - { - public ExampleRow(SourceLineNumber sourceLineNumbers, Table table) - : base(sourceLineNumbers, table) - { - } - - public ExampleRow(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) - : base(sourceLineNumbers, tableDefinition) - { - } - - public string Example - { - get { return (string)this.Fields[0].Data; } - set { this.Fields[0].Data = value; } - } - - public string Value - { - get { return (string)this.Fields[1].Data; } - set { this.Fields[1].Data = value; } - } - } -} diff --git a/src/test/Example.Extension/ExampleSearchSymbol.cs b/src/test/Example.Extension/ExampleSearchSymbol.cs deleted file mode 100644 index 40a39292..00000000 --- a/src/test/Example.Extension/ExampleSearchSymbol.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 Example.Extension -{ - using WixToolset.Data; - - public enum ExampleSearchSymbolFields - { - SearchFor, - } - - public class ExampleSearchSymbol : IntermediateSymbol - { - public ExampleSearchSymbol() : base(ExampleSymbolDefinitions.ExampleSearch, null, null) - { - } - - public ExampleSearchSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.ExampleSearch, sourceLineNumber, id) - { - } - - public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; - - public string SearchFor - { - get => this.Fields[(int)ExampleSearchSymbolFields.SearchFor]?.AsString(); - set => this.Set((int)ExampleSearchSymbolFields.SearchFor, value); - } - } -} diff --git a/src/test/Example.Extension/ExampleSymbol.cs b/src/test/Example.Extension/ExampleSymbol.cs deleted file mode 100644 index 314087e9..00000000 --- a/src/test/Example.Extension/ExampleSymbol.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 Example.Extension -{ - using WixToolset.Data; - - public enum ExampleSymbolFields - { - Value, - } - - public class ExampleSymbol : IntermediateSymbol - { - public ExampleSymbol() : base(ExampleSymbolDefinitions.Example, null, null) - { - } - - public ExampleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.Example, sourceLineNumber, id) - { - } - - public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; - - public string Value - { - get => this.Fields[(int)ExampleSymbolFields.Value]?.AsString(); - set => this.Set((int)ExampleSymbolFields.Value, value); - } - } -} diff --git a/src/test/Example.Extension/ExampleSymbolDefinitions.cs b/src/test/Example.Extension/ExampleSymbolDefinitions.cs deleted file mode 100644 index f13d716d..00000000 --- a/src/test/Example.Extension/ExampleSymbolDefinitions.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 Example.Extension -{ - using System; - using WixToolset.Data; - using WixToolset.Data.Burn; - - public enum ExampleSymbolDefinitionType - { - Example, - ExampleSearch, - } - - public static class ExampleSymbolDefinitions - { - public static readonly IntermediateSymbolDefinition Example = new IntermediateSymbolDefinition( - ExampleSymbolDefinitionType.Example.ToString(), - new[] - { - new IntermediateFieldDefinition(nameof(ExampleSymbolFields.Value), IntermediateFieldType.String), - }, - typeof(ExampleSymbol)); - - public static readonly IntermediateSymbolDefinition ExampleSearch = new IntermediateSymbolDefinition( - ExampleSymbolDefinitionType.ExampleSearch.ToString(), - new[] - { - new IntermediateFieldDefinition(nameof(ExampleSearchSymbolFields.SearchFor), IntermediateFieldType.String), - }, - typeof(ExampleSearchSymbol)); - - static ExampleSymbolDefinitions() - { - ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag); - } - - public static bool TryGetSymbolType(string name, out ExampleSymbolDefinitionType type) - { - return Enum.TryParse(name, out type); - } - - public static IntermediateSymbolDefinition ByName(string name) - { - if (!TryGetSymbolType(name, out var type)) - { - return null; - } - return ByType(type); - } - - public static IntermediateSymbolDefinition ByType(ExampleSymbolDefinitionType type) - { - switch (type) - { - case ExampleSymbolDefinitionType.Example: - return ExampleSymbolDefinitions.Example; - - case ExampleSymbolDefinitionType.ExampleSearch: - return ExampleSymbolDefinitions.ExampleSearch; - - default: - throw new ArgumentOutOfRangeException(nameof(type)); - } - } - } -} diff --git a/src/test/Example.Extension/ExampleTableDefinitions.cs b/src/test/Example.Extension/ExampleTableDefinitions.cs deleted file mode 100644 index a2b81698..00000000 --- a/src/test/Example.Extension/ExampleTableDefinitions.cs +++ /dev/null @@ -1,34 +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 Example.Extension -{ - using WixToolset.Data.WindowsInstaller; - - public static class ExampleTableDefinitions - { - public static readonly TableDefinition ExampleTable = new TableDefinition( - "Wix4Example", - ExampleSymbolDefinitions.Example, - new[] - { - new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), - new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), - }, - strongRowType: typeof(ExampleRow), - symbolIdIsPrimaryKey: true - ); - - public static readonly TableDefinition NotInAll = new TableDefinition( - "TableDefinitionNotExposedByExtension", - null, - new[] - { - new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), - new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), - }, - symbolIdIsPrimaryKey: true - ); - - public static readonly TableDefinition[] All = new[] { ExampleTable }; - } -} diff --git a/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs b/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs deleted file mode 100644 index afccc56f..00000000 --- a/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.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 Example.Extension -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension - { - public override IReadOnlyCollection TableDefinitions => ExampleTableDefinitions.All; - - public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData output, TableDefinitionCollection tableDefinitions) - { - if (ExampleSymbolDefinitions.TryGetSymbolType(symbol.Definition.Name, out var symbolType)) - { - switch (symbolType) - { - case ExampleSymbolDefinitionType.Example: - { - var row = (ExampleRow)this.BackendHelper.CreateRow(section, symbol, output, ExampleTableDefinitions.ExampleTable); - row.Example = symbol.Id.Id; - row.Value = symbol[0].AsString(); - } - return true; - } - } - - return base.TryProcessSymbol(section, symbol, output, tableDefinitions); - } - } -} diff --git a/src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs b/src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs deleted file mode 100644 index a83da7f6..00000000 --- a/src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.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 WixToolsetTest.Core.Burn -{ - using System; - using WixToolset.Core.Burn.Bundles; - using Xunit; - - public class BurnReaderFixture - { - [Fact] - public void CanReadUInt16Max() - { - var bytes = new byte[] { 0xFF, 0xFF }; - var offset = 0u; - - var result = BurnCommon.ReadUInt16(bytes, offset); - - Assert.Equal(UInt16.MaxValue, result); - } - - [Fact] - public void CanReadUInt32Max() - { - var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; - var offset = 0u; - - var result = BurnCommon.ReadUInt32(bytes, offset); - - Assert.Equal(UInt32.MaxValue, result); - } - - [Fact] - public void CanReadUInt64Max() - { - var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - var offset = 0u; - - var result = BurnCommon.ReadUInt64(bytes, offset); - - Assert.Equal(UInt64.MaxValue, result); - } - } -} diff --git a/src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj b/src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj deleted file mode 100644 index 175ee1a9..00000000 --- a/src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - netcoreapp3.1 - false - embedded - - - - NU1701 - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs deleted file mode 100644 index 47b47ef5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ApprovedExeFixture - { - [Fact] - public void CanBuildWithApprovedExe() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.NotEqual(0, result.ExitCode); - Assert.False(File.Exists(exePath)); - } - } - - [Fact] - public void CanBuildWithApprovedExe64() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.NotEqual(0, result.ExitCode); - Assert.False(File.Exists(exePath)); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs deleted file mode 100644 index 62ffe1eb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class BadInputFixture - { - [Fact] - public void SwitchIsNotConsideredAnArgument() - { - var result = WixRunner.Execute(new[] - { - "build", - "-bindpath", "-thisisaswitchnotanarg", - }); - - Assert.Single(result.Messages, m => m.Id == (int)ErrorMessages.Ids.ExpectedArgument); - // TODO: when CantBuildSingleExeBundleWithInvalidArgument is fixed, uncomment: - //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode); - } - - [Fact] - public void HandleInvalidIds() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "InvalidIds.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(330, result.ExitCode); - } - } - - [Fact] - public void CantBuildSingleExeBundleWithInvalidArgument() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - "-nonexistentswitch", "param", - }); - - Assert.NotEqual(0, result.ExitCode); - Assert.False(File.Exists(exePath)); - } - } - - [Fact] - public void RegistryKeyWithoutAttributesDoesntCrash() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "RegistryKey.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.InRange(result.ExitCode, 2, Int32.MaxValue); - } - } - - [Fact] - public void BundleVariableWithBadTypeIsRejected() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleVariable.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(21, result.ExitCode); - } - } - - [Fact] - public void BundleVariableWithHiddenPersistedIsRejected() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(193, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs deleted file mode 100644 index 39e6b4aa..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs +++ /dev/null @@ -1,96 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class BindVariablesFixture - { - [Fact] - public void CanBuildBundleWithPackageBindVariables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - [Fact] - public void CanBuildWithDefaultValue() - { - var folder = TestData.Get(@"TestData", "BindVariables"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DefaultedVariable.wxs"), - "-bf", - "-intermediateFolder", intermediateFolder, - "-bindpath", folder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - } - } - - [Fact] - public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths() - { - var folder = TestData.Get(@"TestData", "WixlibWithBinaries"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - // Use names that aren't excluded in default .gitignores. - "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", - "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(103, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs deleted file mode 100644 index 9bdc9496..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs +++ /dev/null @@ -1,46 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class BootstrapperApplicationFixture - { - [Fact] - public void CanSetBootstrapperApplicationDllDpiAwareness() - { - var folder = TestData.Get(@"TestData\BootstrapperApplication"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DpiAwareness.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var baDllSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(baDllSymbol); - - Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs deleted file mode 100644 index b33b8891..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class BundleExtractionFixture - { - [Fact] - public void CanExtractBundleWithDetachedContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - var baFolderPath = Path.Combine(extractFolderPath, "UX"); - var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer"); - - // TODO: use WixRunner.Execute(string[]) to always go through the command line. - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }, serviceProvider, out var messages).Result; - - WixRunnerResult.AssertSuccess(result, messages); - Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning)); - - Assert.True(File.Exists(exePath)); - - var unbinder = serviceProvider.GetService(); - unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath); - - Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml"))); - Assert.False(Directory.Exists(attachedContainerFolderPath)); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs deleted file mode 100644 index ab644080..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ /dev/null @@ -1,478 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.Burn; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Dtf.Resources; - using Xunit; - - public class BundleFixture - { - [Fact] - public void CanBuildMultiFileBundle() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), - Path.Combine(folder, "MultiFileBundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSimpleBundle() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Bundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - - using (var wixOutput = WixOutput.Read(pdbPath)) - { - - var intermediate = Intermediate.Load(wixOutput); - var section = intermediate.Sections.Single(); - - var bundleSymbol = section.Symbols.OfType().Single(); - Assert.Equal("1.0.0.0", bundleSymbol.Version); - - var previousVersion = bundleSymbol.Fields[(int)WixBundleSymbolFields.Version].PreviousValue; - Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); - - var msiSymbol = section.Symbols.OfType().Single(); - Assert.Equal("test.msi", msiSymbol.Id.Id); - - var extractResult = BundleExtractor.ExtractBAContainer(null, exePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var burnManifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); - var extractedBurnManifestData = File.ReadAllText(Path.Combine(baFolderPath, "manifest.xml"), Encoding.UTF8); - Assert.Equal(extractedBurnManifestData, burnManifestData); - - var baManifestData = wixOutput.GetData(BurnConstants.BootstrapperApplicationDataWixOutputStreamName); - var extractedBaManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BootstrapperApplicationData.xml"), Encoding.UTF8); - Assert.Equal(extractedBaManifestData, baManifestData); - - var bextManifestData = wixOutput.GetData(BurnConstants.BundleExtensionDataWixOutputStreamName); - var extractedBextManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BundleExtensionData.xml"), Encoding.UTF8); - Assert.Equal(extractedBextManifestData, bextManifestData); - - var logElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Log"); - var logElement = (XmlNode)Assert.Single(logElements); - Assert.Equal("", logElement.GetTestXml()); - - var registrationElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration"); - var registrationElement = (XmlNode)Assert.Single(registrationElements); - Assert.Equal($"" + - "" + - "", registrationElement.GetTestXml()); - - var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']"); - var msiPayload = (XmlNode)Assert.Single(msiPayloads); - Assert.Equal("", - msiPayload.GetTestXml(new Dictionary>() { { "Payload", new List { "FileSize", "Hash" } } })); - } - - var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); - manifestResource.Load(exePath); - var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); - Assert.Equal("" + - "" + - "" + - "~TestBundle" + - "" + - "" + - "" + - "true/pmPerMonitorV2, PerMonitor" + - "", actualManifestData); - } - } - - [Fact] - public void CanBuildX64Bundle() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] // TODO: go back to elevating warnings as errors. - { - "build", - "-arch", "x64", - Path.Combine(folder, "Bundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - var warning = Assert.Single(result.Messages.Where(m => m.Level == MessageLevel.Warning)); - Assert.Equal((int)WarningMessages.Ids.ExperimentalBundlePlatform, warning.Id); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - - var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); - manifestResource.Load(exePath); - var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); - Assert.Equal("" + - "" + - "" + - "~TestBundle" + - "" + - "" + - "" + - "true/pmPerMonitorV2, PerMonitor" + - "", actualManifestData); - } - } - - [Fact] - public void CanBuildSimpleBundleUsingExtensionBA() - { - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSingleExeBundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - [Fact] - public void CanBuildSingleExeRemotePayloadBundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - - using (var wixOutput = WixOutput.Read(pdbPath)) - { - var intermediate = Intermediate.Load(wixOutput); - var section = intermediate.Sections.Single(); - - var payloadSymbol = section.Symbols.OfType().Where(x => x.Id.Id == "NetFx462Web").Single(); - Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize); - } - } - } - - [Fact] - public void CantBuildWithDuplicateCacheIds() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal(8001, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithDuplicatePayloadNames() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'Auto2' has a duplicate Name 'burn.exe' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", - }, attachedContainerWarnings); - - var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'DuplicatePayloadNames.wxs' has a duplicate Name 'fakeba.dll' in the BA container. When extracting the container at runtime, the file will get overwritten.", - "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", - "The Payload 'uxYRbgitOs0K878jn5L_z7LdJ21KI' has a duplicate Name 'BundleExtensionData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", - }, baContainerErrors); - - var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", - "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", - }, externalErrors); - - var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.", - }, packageCacheErrors); - - Assert.Equal(14, result.Messages.Length); - } - } - - [Fact] - public void CantBuildWithOrphanPayload() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "OrphanPayload.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithPackageInMultipleContainers() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithUnscheduledPackage() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithUnscheduledRollbackBoundary() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs deleted file mode 100644 index 6d769bd6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ /dev/null @@ -1,365 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class BundleManifestFixture - { - [Fact] - public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA"); - Assert.Equal(3, customElements.Count); - Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); - Assert.Equal("", customElements[2].GetTestXml()); - } - } - - [Fact] - public void PopulatesBAManifestWithPackageInformation() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties"); - var ignoreAttributesByElementName = new Dictionary> - { - { "WixPackageProperties", new List { "DownloadSize", "PackageSize", "InstalledSize", "Version" } }, - }; - Assert.Equal(3, packageElements.Count); - Assert.Equal("", packageElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", packageElements[1].GetTestXml()); - Assert.Equal("", packageElements[2].GetTestXml(ignoreAttributesByElementName)); - } - } - - [Fact] - public void PopulatesBAManifestWithPayloadInformation() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties"); - var ignoreAttributesByElementName = new Dictionary> - { - { "WixPayloadProperties", new List { "Size" } }, - }; - Assert.Equal(4, payloadElements.Count); - Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); - } - } - - [Fact] - public void PopulatesBEManifestWithBundleExtensionBundleCustomData() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE"); - Assert.Equal(3, customElements.Count); - Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); - Assert.Equal("", customElements[2].GetTestXml()); - } - } - - [Fact] - public void PopulatesManifestWithBundleExtension() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"), - Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); - Assert.Equal(1, bundleExtensions.Count); - Assert.Equal("", bundleExtensions[0].GetTestXml()); - - var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); - Assert.Equal(1, bundleExtensionPayloads.Count); - var ignored = new Dictionary> - { - { "Payload", new List { "FileSize", "Hash", "SourcePath" } }, - }; - Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); - } - } - - [Fact] - public void PopulatesManifestWithBundleExtensionSearches() - { - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), - Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); - Assert.Equal(1, bundleExtensions.Count); - Assert.Equal("", bundleExtensions[0].GetTestXml()); - - var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); - Assert.Equal(2, extensionSearches.Count); - Assert.Equal("", extensionSearches[0].GetTestXml()); - Assert.Equal("", extensionSearches[1].GetTestXml()); - - var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); - Assert.Equal(1, bundleExtensionDatas.Count); - Assert.Equal("" + - "" + - "" + - "", bundleExtensionDatas[0].GetTestXml()); - - var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch"); - Assert.Equal(2, exampleSearches.Count); - } - } - - [Fact] - public void PopulatesManifestWithExePackages() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); - var ignoreAttributesByElementName = new Dictionary> - { - { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, - }; - Assert.Equal(2, exePackageElements.Count); - Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); - } - } - - [Fact] - public void PopulatesManifestWithSetVariables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SetVariable", "Simple.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable"); - Assert.Equal(6, setVariables.Count); - Assert.Equal("", setVariables[0].GetTestXml()); - Assert.Equal("", setVariables[1].GetTestXml()); - Assert.Equal("", setVariables[2].GetTestXml()); - Assert.Equal("", setVariables[3].GetTestXml()); - Assert.Equal("", setVariables[4].GetTestXml()); - Assert.Equal("", setVariables[5].GetTestXml()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs deleted file mode 100644 index ad62dea6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs +++ /dev/null @@ -1,107 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class CabFixture - { - [Fact] - public void CabinetFilesSequencedCorrectly() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - Assert.True(File.Exists(cabPath)); - - var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); - var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); - - Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); - Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray()); - - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); - } - } - - [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")] - public void CabinetFilesSequencedCorrectlyUsingMergeModule() - { - var folder = TestData.Get(@"TestData\SimpleMerge"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, ".data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - Assert.True(File.Exists(cabPath)); - - var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); - var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); - - Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray()); - Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray()); - - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); - } - } - - private class FileRow - { - public FileRow(string row) - { - row = row.Substring("File:".Length); - - var split = row.Split('\t'); - this.Id = split[0]; - this.Name = split[2]; - this.Sequence = Convert.ToInt32(split[7]); - } - - public string Id { get; set; } - - public string Name { get; set; } - - public int Sequence { get; set; } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs deleted file mode 100644 index d24ba08c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class ComponentFixture - { - [Fact] - public void CanDetectDuplicateComponentGuids() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Component", "GuidCollision.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); - Array.Equals(new[] - { - 369, - 369 - }, errors.Select(e => e.Id).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs deleted file mode 100644 index dd381dfe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs +++ /dev/null @@ -1,385 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.Burn; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ContainerFixture - { - [Fact(Skip = "Test demonstrates failure")] - public void CanBuildWithCustomAttachedContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); - Assert.Equal(4, payloads.Count); - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); - } - } - - [Fact] - public void HarvestedPayloadsArePutInCorrectContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); - Assert.Equal(4, payloads.Count); - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); - } - } - - [Fact] - public void HarvestedPayloadsArePutInCorrectPackage() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributes = new Dictionary> - { - { "MsiPackage", new List { "CacheId", "InstallSize", "Size", "ProductCode" } }, - { "Provides", new List { "Key" } }, - }; - var msiPackages = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributes)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "", - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "", - }, msiPackages); - } - } - - [Fact] - public void LayoutPayloadIsPutInContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - WixAssert.CompareLineByLine(new string[] - { - "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributes)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, payloads); - } - } - - [Fact] - public void MultipleAttachedContainersAreNotCurrentlySupported() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); - } - } - - [Fact] - public void PayloadIsNotPutInMultipleContainers() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributes)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, payloads); - } - } - - [Fact] - public void PopulatesBAManifestWithLayoutOnlyPayloads() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - WixAssert.CompareLineByLine(new string[] - { - "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributesByElementName = new Dictionary> - { - { "WixPayloadProperties", new List { "Size" } }, - }; - var payloads = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - "", - "", - "", - }, payloads); - } - } - - private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false) - { - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"), - }); - - result.AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs deleted file mode 100644 index c6fa602b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs +++ /dev/null @@ -1,48 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class CopyFileFixture - { - [Fact] - public void CanBuildCopyFile() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CopyFile", "CopyFile.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - var copyFileSymbol = section.Symbols.OfType().Single(); - Assert.Equal("MoveText", copyFileSymbol.Id.Id); - Assert.True(copyFileSymbol.Delete); - Assert.Equal("OtherFolder", copyFileSymbol.DestFolder); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs deleted file mode 100644 index 636b86a6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs +++ /dev/null @@ -1,169 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class CustomActionFixture - { - [Fact] - public void CanDetectCustomActionCycle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - Assert.Equal(176, result.ExitCode); - Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); - } - } - - [Fact] - public void CanDetectCustomActionCycleWithTail() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - Assert.Equal(176, result.ExitCode); - Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); - } - } - - [Fact] - public void PopulatesCustomActionTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { - "ActionText", - "AdminExecuteSequence", - "AdminUISequence", - "AdvtExecuteSequence", - "Binary", - "CustomAction", - "InstallExecuteSequence", - "InstallUISequence", - "Property", - }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); - Assert.Equal(new[] - { - "ActionText:CustomAction2\tProgess2Text\t", - "AdminExecuteSequence:CostFinalize\t\t1000", - "AdminExecuteSequence:CostInitialize\t\t800", - "AdminExecuteSequence:CustomAction2\t\t801", - "AdminExecuteSequence:FileCost\t\t900", - "AdminExecuteSequence:InstallAdminPackage\t\t3900", - "AdminExecuteSequence:InstallFiles\t\t4000", - "AdminExecuteSequence:InstallFinalize\t\t6600", - "AdminExecuteSequence:InstallInitialize\t\t1500", - "AdminExecuteSequence:InstallValidate\t\t1400", - "AdminUISequence:CostFinalize\t\t1000", - "AdminUISequence:CostInitialize\t\t800", - "AdminUISequence:CustomAction2\t\t801", - "AdminUISequence:ExecuteAction\t\t1300", - "AdminUISequence:FileCost\t\t900", - "AdvtExecuteSequence:CostFinalize\t\t1000", - "AdvtExecuteSequence:CostInitialize\t\t800", - "AdvtExecuteSequence:CustomAction2\t\t801", - "AdvtExecuteSequence:InstallFinalize\t\t6600", - "AdvtExecuteSequence:InstallInitialize\t\t1500", - "AdvtExecuteSequence:InstallValidate\t\t1400", - "AdvtExecuteSequence:PublishFeatures\t\t6300", - "AdvtExecuteSequence:PublishProduct\t\t6400", - "Binary:Binary1\t[Binary data]", - "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", - "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t", - "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", - "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", - "InstallExecuteSequence:CostFinalize\t\t1000", - "InstallExecuteSequence:CostInitialize\t\t800", - "InstallExecuteSequence:CreateFolders\t\t3700", - "InstallExecuteSequence:CustomAction2\t\t801", - "InstallExecuteSequence:FileCost\t\t900", - "InstallExecuteSequence:FindRelatedProducts\t\t25", - "InstallExecuteSequence:InstallFiles\t\t4000", - "InstallExecuteSequence:InstallFinalize\t\t6600", - "InstallExecuteSequence:InstallInitialize\t\t1500", - "InstallExecuteSequence:InstallValidate\t\t1400", - "InstallExecuteSequence:LaunchConditions\t\t100", - "InstallExecuteSequence:MigrateFeatureStates\t\t1200", - "InstallExecuteSequence:ProcessComponents\t\t1600", - "InstallExecuteSequence:PublishFeatures\t\t6300", - "InstallExecuteSequence:PublishProduct\t\t6400", - "InstallExecuteSequence:RegisterProduct\t\t6100", - "InstallExecuteSequence:RegisterUser\t\t6000", - "InstallExecuteSequence:RemoveExistingProducts\t\t1401", - "InstallExecuteSequence:RemoveFiles\t\t3500", - "InstallExecuteSequence:RemoveFolders\t\t3600", - "InstallExecuteSequence:UnpublishFeatures\t\t1800", - "InstallExecuteSequence:ValidateProductID\t\t700", - "InstallUISequence:CostFinalize\t\t1000", - "InstallUISequence:CostInitialize\t\t800", - "InstallUISequence:CustomAction2\t\t801", - "InstallUISequence:ExecuteAction\t\t1300", - "InstallUISequence:FileCost\t\t900", - "InstallUISequence:FindRelatedProducts\t\t25", - "InstallUISequence:LaunchConditions\t\t100", - "InstallUISequence:MigrateFeatureStates\t\t1200", - "InstallUISequence:ValidateProductID\t\t700", - "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs deleted file mode 100644 index ee93b03a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs +++ /dev/null @@ -1,234 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Xml.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class CustomTableFixture - { - [Fact] - public void PopulatesCustomTable1() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); - Assert.Equal(new[] - { - "CustomTable1:Row1\ttest.txt", - "CustomTable1:Row2\ttest.txt", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithLocalization() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); - Assert.Equal(new[] - { - "CustomTableLocalized:Row1\tThis is row one", - "CustomTableLocalized:Row2\tThis is row two", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithFilePath() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); - Assert.Equal(new[] - { - "CustomTableWithFile:Row1\t[Binary data]", - "CustomTableWithFile:Row2\t[Binary data]", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithFilePathSerialized() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-lib", wixlibPath, - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); - Assert.Equal(new[] - { - "CustomTableWithFile:Row1\t[Binary data]", - "CustomTableWithFile:Row2\t[Binary data]", - }, results); - } - } - - [Fact] - public void UnrealCustomTableIsNotPresentInMsi() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); - Assert.Empty(results); - } - } - - [Fact] - public void CanCompileAndDecompile() - { - var folder = TestData.Get(@"TestData"); - var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs"); - - var result = WixRunner.Execute(new[] - { - "build", - "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - Assert.True(File.Exists(msiPath)); - - result = WixRunner.Execute(new[] - { - "decompile", msiPath, - "-sw1060", - "-intermediateFolder", intermediateFolder, - "-o", decompiledWxsPath - }); - - result.AssertSuccess(); - - WixAssert.CompareXml(expectedFile, decompiledWxsPath); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs deleted file mode 100644 index ab04da15..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class DecompileFixture - { - private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName) - { - var folder = TestData.Get(sourceFolder); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, msiName), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath); - } - } - - [Fact] - public void CanDecompileSingleFileCompressed() - { - DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs"); - } - - [Fact] - public void CanDecompile64BitSingleFileCompressed() - { - DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs"); - } - - [Fact] - public void CanDecompileNestedDirSearchUnderRegSearch() - { - DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs"); - } - - [Fact] - public void CanDecompileOldClassTableDefinition() - { - // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. - // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, - // and numerous foreign key relationships are missing. - DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs"); - } - - [Fact] - public void CanDecompileSequenceTables() - { - DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs"); - } - - [Fact] - public void CanDecompileShortcuts() - { - DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); - } - - [Fact] - public void CanDecompileNullComponent() - { - DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); - } - - [Fact] - public void CanDecompileMergeModuleWithTargetDirComponent() - { - DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs"); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs deleted file mode 100644 index 840b411e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs +++ /dev/null @@ -1,180 +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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class DependencyExtensionFixture - { - [Fact] - public void CanBuildBundleUsingExePackageWithProvides() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides") - .Cast() - .Select(e => e.GetTestXml()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, provides); - } - } - - [Fact] - public void CanBuildBundleUsingMsiWithProvides() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "UsingProvides", "Package.wxs"), - Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "UsingProvides"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "UsingProvides.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides") - .Cast() - .Select(e => e.GetTestXml()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - "", - }, provides); - } - } - - [Fact] - public void CanBuildBundleWithCustomProviderKey() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributesByElementName = new Dictionary> - { - { "Registration", new List { "Id" } }, - }; - var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, registration); - } - } - - [Fact] - public void CanBuildPackageUsingProvides() - { - var folder = TestData.Get(@"TestData\UsingProvides"); - var build = new Builder(folder, null, new[] { folder }); - - var results = build.BuildAndQuery(Build, "WixDependencyProvider"); - Assert.Equal(new[] - { - "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t", - }, results); - } - - private static void Build(string[] args) - { - var result = WixRunner.Execute(args) - .AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs deleted file mode 100644 index a61bdff3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ /dev/null @@ -1,271 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class DirectoryFixture - { - [Fact] - public void CanGet32bitProgramFiles6432Folder() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Directory", "Empty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", - "ProgramFiles6432Folder:ProgramFilesFolder:.", - "ProgramFilesFolder:TARGETDIR:PFiles", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - } - } - - [Fact] - public void CanGet64bitProgramFiles6432Folder() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "Directory", "Empty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", - "ProgramFiles6432Folder:ProgramFiles64Folder:.", - "ProgramFiles64Folder:TARGETDIR:PFiles64", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - } - } - - [Fact] - public void CanGetDefaultName() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Directory", "DefaultName.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - WixAssert.CompareLineByLine(new[] - { - "BinFolder\tCompanyFolder\t.", - "CompanyFolder\tProgramFilesFolder\tExample Corporation", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - WixAssert.CompareLineByLine(new[] - { - "BinFolder\tCompanyFolder\t.", - "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); - } - } - - [Fact] - public void CanGetDuplicateDir() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe", - "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", - "ProgramFiles6432Folder:ProgramFiles64Folder:.", - "ProgramFiles64Folder:TARGETDIR:PFiles64", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - } - } - - [Fact] - public void CanGetWithMultiNestedSubdirectory() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "Directory", "Nested.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin", - "ProgramFilesFolder:TARGETDIR:PFiles", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - Assert.Equal(new[] - { - "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", - "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", - "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", - "ProgramFilesFolder:TARGETDIR:PFiles", - "TARGETDIR::SourceDir" - }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); - } - } - - [Fact] - public void CanGetDuplicateTargetSourceName() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "BinFolder\tProgramFilesFolder\tbin", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - Assert.Equal(new[] - { - "BinFolder\tProgramFilesFolder\tbin", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs deleted file mode 100644 index e2306dcd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs +++ /dev/null @@ -1,52 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ExePackageFixture - { - [Fact] - public void ErrorWhenMissingDetectCondition() - { - var folder = TestData.Get(@"TestData", "ExePackage"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MissingDetectCondition.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(1153, result.ExitCode); - } - } - - [Fact] - public void ErrorWhenRequireDetectCondition() - { - var folder = TestData.Get(@"TestData", "ExePackage"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "RequireDetectCondition.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(401, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs deleted file mode 100644 index 089658e6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs +++ /dev/null @@ -1,153 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class ExtensionFixture - { - [Fact] - public void CanBuildAndQuery() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var build = new Builder(folder, typeof(ExampleExtensionFactory), new[] { Path.Combine(folder, "data") }); - - var results = build.BuildAndQuery(Build, "Wix4Example"); - Assert.Equal(new[] - { - "Wix4Example:Foo\tBar" - }, results); - } - - [Fact] - public void CanBuildWithExampleExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); - Assert.Equal("Foo", example.Id?.Id); - Assert.Equal("Bar", example[0].AsString()); - } - } - - [Fact] - public void CanParseCommandLineWithExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-example", "test", - "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); - var section = intermediate.Sections.Single(); - - var property = section.Symbols.OfType().Where(p => p.Id.Id == "ExampleProperty").Single(); - Assert.Equal("ExampleProperty", property.Id.Id); - Assert.Equal("test", property.Value); - } - } - - [Fact] - public void CannotBuildWithMissingExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var exception = Assert.Throws(() => - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-ext", "ExampleExtension.DoesNotExist" - })); - - Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist' could not be found. Checked paths: ", exception.Message); - } - } - - [Fact] - public void CannotBuildWithMissingVersionedExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var exception = Assert.Throws(() => - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-ext", "ExampleExtension.DoesNotExist/1.0.0" - })); - - Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist/1.0.0' could not be found. Checked paths: ", exception.Message); - } - } - - private static void Build(string[] args) - { - var result = WixRunner.Execute(args) - .AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs deleted file mode 100644 index db9708a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs +++ /dev/null @@ -1,174 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class LanguageFixture - { - [Fact] - public void CanBuildWithDefaultProductLanguage() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var directorySymbols = section.Symbols.OfType(); - WixAssert.CompareLineByLine(new[] - { - "INSTALLFOLDER:Example Corporation\\MsiPackage", - "ProgramFilesFolder:PFiles", - "TARGETDIR:SourceDir" - }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray()); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("0", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;0", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("1252", summaryCodepage.Value); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - WixAssert.CompareLineByLine(new[] - { - "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", - "INSTALLFOLDER:oekcr5lq|MsiPackage", - "ProgramFilesFolder:PFiles", - "TARGETDIR:SourceDir" - }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); - } - } - - [Fact] - public void CanBuildEnuWxl() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("1033", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;1033", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("1252", summaryCodepage.Value); - } - } - - [Fact] - public void CanBuildJpnWxl() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.ja-jp.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("1041", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;1041", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("932", summaryCodepage.Value); - } - } - - [Fact] - public void CanBuildJpnWxlWithEnuSummaryInfo() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("1041", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;1041", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("1252", summaryCodepage.Value); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs deleted file mode 100644 index cfe4d3f1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs +++ /dev/null @@ -1,174 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class LinkerFixture - { - [Fact] - public void MustCompileBeforeLinking() - { - var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null); - var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null); - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - - var listener = new TestMessageListener(); - var messaging = serviceProvider.GetService(); - messaging.SetListener(listener); - - var creator = serviceProvider.GetService(); - var context = serviceProvider.GetService(); - context.Extensions = Array.Empty(); - context.ExtensionData = Array.Empty(); - context.Intermediates = new[] { intermediate1, intermediate2 }; - context.SymbolDefinitionCreator = creator; - - var linker = serviceProvider.GetService(); - linker.Link(context); - - Assert.Equal((int)ErrorMessages.Ids.IntermediatesMustBeCompiled, messaging.LastErrorNumber); - Assert.Single(listener.Messages); - Assert.EndsWith("TestIntermediate1, TestIntermediate2", listener.Messages[0].ToString()); - } - - [Fact] - public void CanBuildWithOverridableActions() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1008", // this is expected for this test - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var actions = section.Symbols.OfType().Where(wat => wat.Action.StartsWith("Set")).ToList(); - Assert.Equal(2, actions.Count); - //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileSymbolFields.Source].AsPath().Path); - //Assert.Equal(@"test.txt", wixFile[WixFileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void MissingEntrySectionDetectedProduct() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - try - { - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - } - catch (WixException we) - { - Assert.Equal("Could not find entry section in provided list of intermediates. Expected section of type 'Product'.", we.Message); - return; - } - - Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); - } - } - - [Fact] - public void MissingEntrySectionDetectedWixipl() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - try - { - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.wixipl") - }); - } - catch (WixException we) - { - Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); - return; - } - - Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); - } - } - - [Fact] - public void MissingEntrySectionDetectedUnknown() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - try - { - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.bob") - }); - } - catch (WixException we) - { - Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); - return; - } - - Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs deleted file mode 100644 index de18e30c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MediaFixture.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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class MediaFixture - { - [Fact] - public void CanBuildMultiMedia() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Media", "MultiMedia.wxs"), - "-bindpath", Path.Combine(folder, "Media", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var mediaSymbols = section.Symbols.OfType().OrderBy(m => m.DiskId).ToList(); - var fileSymbols = section.Symbols.OfType().OrderBy(f => f.Sequence).ToList(); - Assert.Equal(1, mediaSymbols[0].DiskId); - Assert.Equal(2, mediaSymbols[0].LastSequence); - Assert.Equal(2, mediaSymbols[1].DiskId); - Assert.Equal(4, mediaSymbols[1].LastSequence); - Assert.Equal(new[] - { - "a1.txt", - "a2.txt", - "b1.txt", - "b2.txt", - }, fileSymbols.Select(f => f.Name).ToArray()); - Assert.Equal(new[] - { - 1, - 2, - 3, - 4, - }, fileSymbols.Select(f => f.Sequence).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs deleted file mode 100644 index 17e91692..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class ModuleFixture - { - [Fact] - public void CanBuildSimpleModule() - { - var folder = TestData.Get(@"TestData\SimpleModule"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Module.wxs"), - "-loc", Path.Combine(folder, "Module.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msm") - }); - - result.AssertSuccess(); - - var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); - Assert.True(File.Exists(msmPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); - WixAssert.CompareLineByLine(new[] - { - "MergeRedirectFolder\tTARGETDIR\t.", - "NotTheMergeRedirectFolder\tTARGETDIR\t.", - "TARGETDIR\t\tSourceDir" - }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - - var fileSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); - WixAssert.CompareLineByLine(new[] - { - $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", - $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", - }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var fileRows = data.Tables["File"].Rows; - Assert.Equal(new[] - { - "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); - - var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); - Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(new[] - { - "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); - } - } - - [Fact] - public void CanSuppressModularization() - { - var folder = TestData.Get(@"TestData\SuppressModularization"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Module.wxs"), - "-loc", Path.Combine(folder, "Module.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-sw1079", - "-sw1086", - "-o", Path.Combine(intermediateFolder, @"bin\test.msm") - }); - - result.AssertSuccess(); - - var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); - - var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" }); - WixAssert.CompareLineByLine(new[] - { - "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t", - "Property:MsiHiddenProperties\tTest" - }, rows); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs deleted file mode 100644 index 3bdfa0ef..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ /dev/null @@ -1,838 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class MsiFixture - { - [Fact] - public void CanBuildSingleFile() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Compiled)); - Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Resolved)); - Assert.True(intermediate.HasLevel(WixToolset.Data.WindowsInstaller.IntermediateLevels.FullyBound)); - - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildSingleFileCompressed() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildSingleFileCompressedWithMediaTemplate() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel=low", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildMultipleFilesCompressed() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1079", // TODO: why does this test need to create a second cab which is empty? - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanFailBuildMissingFile() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "does-not-exist"), - "-bindpath", Path.Combine(folder, "also-does-not-exist"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }, out var messages); - Assert.Equal(103, result); - - var error = messages.Single(m => m.Level == MessageLevel.Error); - var errorMessage = error.ToString(); - var checkedPaths = errorMessage.Substring(errorMessage.IndexOf(':') + 1).Split(new[] { ',' }).Select(s => s.Trim()).ToArray(); - Assert.Equal(new[] - { - "test.txt", - Path.Combine(folder, "does-not-exist", "test.txt"), - Path.Combine(folder, "also-does-not-exist", "test.txt"), - }, checkedPaths); - } - } - - [Fact] - public void CanBuildWithErrorTable() - { - var folder = TestData.Get(@"TestData\ErrorsInUI"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var errors = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - Assert.Equal("Category 55 Emergency Doomsday Crisis", errors["1234"].Message.Trim()); - Assert.Equal(" ", errors["5678"].Message); - - var customAction1 = section.Symbols.OfType().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single(); - Assert.Equal("1234", customAction1.Target); - - var customAction2 = section.Symbols.OfType().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single(); - Assert.Equal("If you see this, something went wrong.", customAction2.Target); - } - } - - [Fact] - public void CanLoadPdbGeneratedByBuild() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - - var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); - Assert.True(File.Exists(pdbPath)); - - var output = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: true); - Assert.NotNull(output); - } - } - - [Fact] - public void CanLoadPdbGeneratedByBuildViaWixOutput() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - - var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); - Assert.True(File.Exists(pdbPath)); - - var wixOutput = WixOutput.Read(pdbPath); - var output = WindowsInstallerData.Load(wixOutput, suppressVersionCheck: true); - Assert.NotNull(output); - } - } - - [Fact] - public void CanBuildManualUpgrade() - { - var folder = TestData.Get(@"TestData\ManualUpgrade"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }, out var messages); - - Assert.Equal(0, result); - - var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(pdbPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(pdbPath); - var section = intermediate.Sections.Single(); - - var upgradeSymbol = section.Symbols.OfType().Single(); - Assert.False(upgradeSymbol.ExcludeLanguages); - Assert.True(upgradeSymbol.IgnoreRemoveFailures); - Assert.False(upgradeSymbol.VersionMaxInclusive); - Assert.True(upgradeSymbol.VersionMinInclusive); - Assert.Equal("13.0.0", upgradeSymbol.VersionMax); - Assert.Equal("12.0.0", upgradeSymbol.VersionMin); - Assert.False(upgradeSymbol.OnlyDetect); - Assert.Equal("BLAHBLAHBLAH", upgradeSymbol.ActionProperty); - - var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false); - var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single(); - Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1)); - } - } - - [Fact] - public void CanBuildWixipl() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.wixipl") - }, out var messages); - - Assert.Equal(0, result); - - var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); - - Assert.Equal(new[]{ - "test.wixipl" - }, builtFiles.Select(Path.GetFileName).ToArray()); - } - } - - [Fact] - public void CanBuildWixlib() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.wixlib") - }, out var messages); - - Assert.Equal(0, result); - - var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); - - Assert.Equal(new[]{ - "test.wixlib" - }, builtFiles.Select(Path.GetFileName).ToArray()); - } - } - - [Fact] - public void CanBuildBinaryWixlib() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute( - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-bindfiles", - "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); - - result.AssertSuccess(); - - using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) - { - Assert.NotNull(wixout.GetDataStream("wix-ir.json")); - - var text = wixout.GetData("wix-ir/test.txt"); - Assert.Equal("This is test.txt.", text); - } - } - } - - [Fact] - public void CanBuildBinaryWixlibWithCollidingFilenames() - { - var folder = TestData.Get(@"TestData\SameFileFolders"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute( - "build", - Path.Combine(folder, "TestComponents.wxs"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-bindfiles", - "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); - - result.AssertSuccess(); - - using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) - { - Assert.NotNull(wixout.GetDataStream("wix-ir.json")); - - var text = wixout.GetData("wix-ir/test.txt"); - Assert.Equal(@"This is a\test.txt.", text); - - var text2 = wixout.GetData("wix-ir/test.txt-1"); - Assert.Equal(@"This is b\test.txt.", text2); - - var text3 = wixout.GetData("wix-ir/test.txt-2"); - Assert.Equal(@"This is c\test.txt.", text3); - } - } - } - - [Fact] - public void CanBuildWithIncludePath() - { - var folder = TestData.Get(@"TestData\IncludePath"); - var bindpath = Path.Combine(folder, "data"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute( - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", bindpath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi"), - "-i", bindpath); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildWithAssembly() - { - var folder = TestData.Get(@"TestData\Assembly"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\candle.exe"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"candle.exe", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var msiAssemblyNameSymbols = section.Symbols.OfType(); - Assert.Equal(new[] - { - "culture", - "fileVersion", - "name", - "processorArchitecture", - "publicKeyToken", - "version" - }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Name).ToArray()); - - Assert.Equal(new[] - { - "neutral", - "3.11.11810.0", - "candle", - "x86", - "256B3414DFA97718", - "3.0.0.0" - }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Value).ToArray()); - } - } - - [Fact] - public void CanBuild64bit() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var platformSummary = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("x64;1033", platformSummary.Value); - } - } - - [Fact] - public void CanBuildSharedComponent() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - // Only one component is shared. - var sharedComponentSymbols = section.Symbols.OfType(); - Assert.Equal(1, sharedComponentSymbols.Sum(t => t.Shared ? 1 : 0)); - - // And it is this one. - var sharedComponentSymbol = sharedComponentSymbols.Single(t => t.Id.Id == "Shared.dll"); - Assert.True(sharedComponentSymbol.Shared); - } - } - - [Fact] - public void CanBuildSetProperty() - { - var folder = TestData.Get(@"TestData\SetProperty"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var output = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); - var caRows = output.Tables["CustomAction"].Rows.Single(); - Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); - Assert.Equal("51", caRows.FieldAsString(1)); - Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); - Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3)); - } - } - - [Fact] - public void CanBuildVersionIndependentProgId() - { - var folder = TestData.Get(@"TestData\ProgId"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var progids = section.Symbols.OfType().OrderBy(symbol => symbol.ProgId).ToList(); - Assert.Equal(new[] - { - "Foo.File.hol", - "Foo.File.hol.15" - }, progids.Select(p => p.ProgId).ToArray()); - - Assert.Equal(new[] - { - "Foo.File.hol.15", - null - }, progids.Select(p => p.ParentProgIdRef).ToArray()); - } - } - - [Fact] - public void CanBuildInstanceTransform() - { - var folder = TestData.Get(@"TestData\InstanceTransform"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); - var substorage = output.SubStorages.Single(); - Assert.Equal("I1", substorage.Name); - - var data = substorage.Data; - Assert.Equal(new[] - { - "_SummaryInformation", - "Property", - "Upgrade" - }, data.Tables.Select(t => t.Name).ToArray()); - - Assert.Equal(new[] - { - "INSTANCEPROPERTY\tI1", - "ProductName\tMsiPackage (Instance 1)", - }, JoinRows(data.Tables["Property"])); - - Assert.Equal(new[] - { - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0", - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0" - }, JoinRows(data.Tables["Upgrade"])); - } - } - - [Fact(Skip = "Test demonstrates failure")] - public void FailsBuildAtLinkTimeForMissingEnsureTable() - { - var folder = TestData.Get(@"TestData"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - Assert.Collection(result.Messages, - first => - { - Assert.Equal(MessageLevel.Error, first.Level); - Assert.Equal("The identifier 'WixCustomTable:TableDefinitionNotExposedByExtension' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.", first.ToString()); - }); - - Assert.False(File.Exists(msiPath)); - } - } - - private static string[] JoinRows(Table table) - { - return table.Rows.Select(r => JoinFields(r.Fields)).ToArray(); - - string JoinFields(Field[] fields) - { - return String.Join('\t', fields.Select(f => f.ToString())); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs deleted file mode 100644 index 71edddc6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ /dev/null @@ -1,1040 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class MsiQueryFixture - { - [Fact] - public void PopulatesAppIdTableWhenAdvertised() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppId", "Advertised.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); - WixAssert.CompareLineByLine(new[] - { - "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromComponentSearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", - "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromDirectorySearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch", - "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromFileSearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "FileSearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", - "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", - "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromRegistrySearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", - "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromRegistrySearch64() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", - "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18", - }, results); - } - } - - [Fact] - public void PopulatesClassTablesWhenIconIndexIsZero() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Class", "IconIndex0.wxs"), - Path.Combine(folder, "Icon", "SampleIcon.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Class" }); - WixAssert.CompareLineByLine(new[] - { - "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", - }, results); - } - } - - [Fact] - public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", - "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", - "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp", - "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp", - "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp", - }, results); - } - } - - [Fact] - public void PopulatesControlTables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - - var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" }); - WixAssert.CompareLineByLine(new[] - { - "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", - "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t", - "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t", - "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun", - "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t", - "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled", - "ControlCondition:FirstDialog\tHeader\tHide\tInstalled", - "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed", - "InstallUISequence:CostFinalize\t\t1000", - "InstallUISequence:CostInitialize\t\t800", - "InstallUISequence:ExecuteAction\t\t1300", - "InstallUISequence:FileCost\t\t900", - "InstallUISequence:FindRelatedProducts\t\t25", - "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298", - "InstallUISequence:LaunchConditions\t\t100", - "InstallUISequence:MigrateFeatureStates\t\t1200", - "InstallUISequence:SecondDialog\tNOT Installed\t1299", - "InstallUISequence:ValidateProductID\t\t700", - }, results); - } - } - - [Fact] - public void PopulatesCreateFolderTableForNullKeypathComponents() - { - var folder = TestData.Get(@"TestData\Components"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); - WixAssert.CompareLineByLine(new[] - { - "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", - }, results); - } - } - - [Fact] - public void PopulatesDirectoryTableWithValidDefaultDir() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1031", // this is expected for this test - Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); - WixAssert.CompareLineByLine(new[] - { - "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", - "Directory:Folder1\tINSTALLFOLDER\tFolder.1", - "Directory:Folder12\tINSTALLFOLDER\tFolder.12", - "Directory:Folder123\tINSTALLFOLDER\tFolder.123", - "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234", - "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", - "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", - "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", - "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", - "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.", - "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles", - "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", - "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", - "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", - "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly", - "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL", - "Directory:TARGETDIR\t\tSourceDir", - }, results); - } - } - - [Fact] - public void PopulatesEnvironmentTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Environment", "Environment.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); - WixAssert.CompareLineByLine(new[] - { - "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", - "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", - "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", - "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", - "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest", - }, results); - } - } - - [Fact(Skip = "Test demonstrates failure")] - public void PopulatesExampleTableBecauseOfEnsureTable() - { - var folder = TestData.Get(@"TestData"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" }); - Assert.Empty(results["Wix4Example"]); - } - } - - [Fact] - public void PopulatesFeatureTableWithParent() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); - WixAssert.CompareLineByLine(new[] - { - "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", - "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", - "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0", - }, results); - } - } - - [Fact] - public void PopulatesFontTableFromFontTitle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Font", "FontTitle.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Font" }); - WixAssert.CompareLineByLine(new[] - { - "Font:test.txt\tFakeFont", - }, results); - } - } - - [Fact] - public void PopulatesFontTableFromTrueType() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Font", "TrueType.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Font" }); - WixAssert.CompareLineByLine(new[] - { - "Font:TrueTypeFontFile\t", - }, results); - } - } - - [Fact] - public void PopulatesInstallExecuteSequenceTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" }); - WixAssert.CompareLineByLine(new[] - { - "InstallExecuteSequence:CostFinalize\t\t1000", - "InstallExecuteSequence:CostInitialize\t\t800", - "InstallExecuteSequence:CreateFolders\t\t3700", - "InstallExecuteSequence:FileCost\t\t900", - "InstallExecuteSequence:FindRelatedProducts\t\t25", - "InstallExecuteSequence:InstallFiles\t\t4000", - "InstallExecuteSequence:InstallFinalize\t\t6600", - "InstallExecuteSequence:InstallInitialize\t\t1500", - "InstallExecuteSequence:InstallValidate\t\t1400", - "InstallExecuteSequence:LaunchConditions\t\t100", - "InstallExecuteSequence:MigrateFeatureStates\t\t1200", - "InstallExecuteSequence:ProcessComponents\t\t1600", - "InstallExecuteSequence:PublishFeatures\t\t6300", - "InstallExecuteSequence:PublishProduct\t\t6400", - "InstallExecuteSequence:RegisterProduct\t\t6100", - "InstallExecuteSequence:RegisterUser\t\t6000", - "InstallExecuteSequence:RemoveExistingProducts\t\t1401", - "InstallExecuteSequence:RemoveFiles\t\t3500", - "InstallExecuteSequence:RemoveFolders\t\t3600", - "InstallExecuteSequence:UnpublishFeatures\t\t1800", - "InstallExecuteSequence:ValidateProductID\t\t700", - }, results); - } - } - - [Fact] - public void PopulatesLockPermissionsTableWithEmptyPermissions() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); - WixAssert.CompareLineByLine(new[] - { - "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", - }, results); - } - } - - [Fact] - public void PopulatesMsiAssemblyTables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Assembly", "Win32Assembly.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "Assembly", "data"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); - WixAssert.CompareLineByLine(new[] - { - "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", - "MsiAssemblyName:test.txt\tname\tMyApplication.app", - "MsiAssemblyName:test.txt\tversion\t1.0.0.0", - }, results); - } - } - - [Fact] - public void PopulatesReserveCostTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); - WixAssert.CompareLineByLine(new[] - { - "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", - }, results); - } - } - - [Fact] - public void PopulatesServiceTables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); - WixAssert.CompareLineByLine(new[] - { - "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", - "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", - }, results); - } - } - - [Fact] - public void PopulatesTextStyleTableWhenColorIsNull() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "TextStyle", "ColorNull.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); - WixAssert.CompareLineByLine(new[] - { - "TextStyle:FirstTextStyle\tArial\t2\t\t", - }, results); - } - } - - [Fact] - public void PopulatesTextStyleTableWhenSizeIsLocalized() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); - WixAssert.CompareLineByLine(new[] - { - "TextStyle:CustomFont\tTahoma\t8\t\t", - }, results); - } - } - - [Fact] - public void PopulatesTypeLibTableWhenLanguageIsZero() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "TypeLib", "Language0.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); - WixAssert.CompareLineByLine(new[] - { - "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", - }, results); - } - } - - [Fact] - public void PopulatesUpgradeTableFromManualUpgrade() - { - var folder = TestData.Get(@"TestData\ManualUpgrade"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }, out var messages); - - Assert.Equal(0, result); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); - WixAssert.CompareLineByLine(new[] - { - "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", - }, results); - } - } - - [Fact] - public void PopulatesUpgradeTableFromDetectOnlyUpgrade() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); - WixAssert.CompareLineByLine(new[] - { - "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", - "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", - "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", - }, results); - - var prefix = "Property:SecureCustomProperties\t"; - var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); - WixAssert.CompareLineByLine(new[] - { - "RELPRODFOUND", - "WIX_DOWNGRADE_DETECTED", - "WIX_UPGRADE_DETECTED", - }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray()); - } - } - - [Fact] - public void CanMergeModule() - { - var folder = TestData.Get(@"TestData\SimpleMerge"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); - var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, ".data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - Assert.Empty(section.Symbols.OfType()); - - var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - Assert.Empty(data.Tables["File"].Rows); - - var results = Query.QueryDatabase(msiPath, new[] { "File" }); - WixAssert.CompareLineByLine(new[] - { - "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" - }, results); - - var files = Query.GetCabinetFiles(cabPath); - WixAssert.CompareLineByLine(new[] - { - "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" - }, files.Select(f => f.Name).ToArray()); - } - } - - [Fact] - public void CanPublishComponentWithMultipleFeatureComponents() - { - var folder = TestData.Get(@"TestData\PublishComponent"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" }); - WixAssert.CompareLineByLine(new[] - { - "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2", - "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs deleted file mode 100644 index a566b490..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs +++ /dev/null @@ -1,131 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class MsiTransactionFixture - { - [Fact] - public void CantBuildX64AfterX86Bundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var exePath = Path.Combine(binFolder, "test.exe"); - - BuildMsiPackages(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1151", // this is expected for this test - Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal(390, result.ExitCode); - } - } - - [Fact] - public void CanBuildX86AfterX64Bundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var exePath = Path.Combine(binFolder, "test.exe"); - - BuildMsiPackages(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1151", // this is expected for this test - Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder) - { - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"), - }); - - result.AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs deleted file mode 100644 index 475afcf0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs +++ /dev/null @@ -1,36 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class MsuPackageFixture - { - [Fact] - public void CanBuildBundleWithMsuPackage() - { - var folder = TestData.Get(@"TestData", "MsuPackage"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, "bin", "test.exe") - }); - - result.AssertSuccess(); - Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe"))); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs deleted file mode 100644 index 6b2d8bfa..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs +++ /dev/null @@ -1,211 +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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class PackagePayloadFixture - { - [Fact] - public void CanSpecifyPackagePayloadInPayloadGroup() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); - var ignoreAttributesByElementName = new Dictionary> - { - { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, - }; - Assert.Equal(1, exePackageElements.Count); - Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); - - var payloadElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='burn.exe']"); - Assert.Equal(1, payloadElements.Count); - Assert.Equal("", payloadElements[0].GetTestXml()); - } - } - - [Fact] - public void ErrorWhenMissingSourceFileAndHash() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "MissingSourceFileAndHash.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(44, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenMissingSourceFileAndName() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "MissingSourceFileAndName.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(44, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenSpecifiedHash() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SpecifiedHash.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(4, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MspPackagePayload element contains an unexpected attribute 'Hash'.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenSpecifiedHashAndMissingDownloadUrl() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(10, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenSpecifiedSourceFileAndHash() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(35, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenWrongPackagePayloadInPayloadGroup() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - Assert.Equal(407, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The ExePackagePayload element can only be used for ExePackages.", - "The location of the package related to previous error.", - "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs deleted file mode 100644 index cdba85de..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs +++ /dev/null @@ -1,36 +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 WixToolsetTest.CoreIntegration -{ - using System.Linq; - using WixToolset.Core; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class ParseFixture - { - [Fact] - public void GeneratesCorrectCustomActionIdentifiers() - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var section = new IntermediateSection("section", SectionType.Fragment); - var parseHelper = serviceProvider.GetService(); - - parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86); - parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86); - parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); - parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86); - parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64); - - var simpleReferences = section.Symbols.OfType(); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction32_X86").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_X86").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_A64").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X86").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X64").FirstOrDefault()); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs deleted file mode 100644 index 483e3fd5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ /dev/null @@ -1,279 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using System.Xml; - using System.Xml.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Burn; - using Xunit; - - public class PatchFixture - { - private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; - - [Fact] - public void CanBuildSimplePatch() - { - var folder = TestData.Get(@"TestData\PatchSingle"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); - var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); - - var doc = GetExtractPatchXml(patchPath); - Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); - - var names = Query.GetSubStorageNames(patchPath); - Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); - - var cab = Path.Combine(tempFolder, "foo.cab"); - Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); - - var files = Query.GetCabinetFiles(cab); - Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); - } - } - - [Fact] - public void CanBuildSimplePatchWithNoFileChanges() - { - var folder = TestData.Get(@"TestData\PatchNoFileChanges"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); - var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); - - var doc = GetExtractPatchXml(patchPath); - Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); - - var names = Query.GetSubStorageNames(patchPath); - Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); - - var cab = Path.Combine(tempFolder, "foo.cab"); - Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); - - var files = Query.GetCabinetFiles(cab); - Assert.Empty(files); - } - } - - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")] - public void CanBuildPatchFromProductWithFilesFromWixlib() - { - var folder = TestData.Get(@"TestData\PatchFromWixlib"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolderBaseline = fs.GetFolder(); - var tempFolderUpdate = fs.GetFolder(); - var tempFolderPatch = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); - var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); - } - } - - [Fact] - public void CanBuildBundleWithNonSpecificPatches() - { - var folder = TestData.Get(@"TestData\PatchNonSpecific"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B"); - var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B"); - var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true); - var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true); - var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true); - var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); - var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder); - var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder); - - VerifyPatchTargetCodes(bundleAPdb, new[] - { - "", - }); - VerifyPatchTargetCodes(bundleBPdb, new[] - { - "", - "", - }); - VerifyPatchTargetCodes(bundleCPdb, new string[0]); - } - } - - [Fact] - public void CanBuildBundleWithSlipstreamPatch() - { - var folder = TestData.Get(@"TestData\PatchSingle"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); - var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); - - using (var wixOutput = WixOutput.Read(bundleAPdb)) - { - var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); - var doc = new XmlDocument(); - doc.LoadXml(manifestData); - var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); - var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr); - Assert.Equal(1, slipstreamMspNodes.Count); - Assert.Equal("", slipstreamMspNodes[0].GetTestXml()); - } - } - } - - private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) - { - using (var wixOutput = WixOutput.Read(pdbPath)) - { - var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); - var doc = new XmlDocument(); - doc.LoadXml(manifestData); - var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); - var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr); - - var actual = new List(); - foreach (XmlNode patchTargetCodeNode in patchTargetCodes) - { - actual.Add(patchTargetCodeNode.GetTestXml()); - } - - WixAssert.CompareLineByLine(expected, actual.ToArray()); - } - } - - private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) - { - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(sourceFolder, @"Package.wxs"), - "-d", "V=" + defineV, - "-d", "A=" + defineA, - "-d", "B=" + defineB, - "-bindpath", Path.Combine(sourceFolder, ".data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath, - "-ext", extensionPath, - }); - - result.AssertSuccess(); - - return Path.ChangeExtension(outputPath, ".wixpdb"); - } - - private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, bool hasNoFiles = false) - { - var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); - - var args = new List - { - "build", - hasNoFiles ? "-sw1079" : " ", - Path.Combine(sourceFolder, @"Patch.wxs"), - "-d", "V=" + defineV, - "-bindpath", Path.Combine(baseFolder, "bin"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath - }; - - foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty()) - { - args.Add("-bindpath"); - args.Add(additionaBindPath); - } - - var result = WixRunner.Execute(args.ToArray()); - - result.AssertSuccess(); - - return Path.ChangeExtension(outputPath, ".wixpdb"); - } - - private static string BuildBundle(string outputName, string sourceFolder, string baseFolder) - { - var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(sourceFolder, @"Bundle.wxs"), - Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"), - "-bindpath", Path.Combine(baseFolder, "bin"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath - }); - - result.AssertSuccess(); - - return Path.ChangeExtension(outputPath, ".wixpdb"); - } - - private static XDocument GetExtractPatchXml(string path) - { - var buffer = new StringBuilder(65535); - var size = buffer.Capacity; - - var er = MsiExtractPatchXMLData(path, 0, buffer, ref size); - if (er != 0) - { - throw new Win32Exception(er); - } - - return XDocument.Parse(buffer.ToString()); - } - - [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData); - - [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs deleted file mode 100644 index 23f6a9ba..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ /dev/null @@ -1,212 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class PayloadFixture - { - [Fact] - public void CanParseValidName() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ValidName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - Assert.Empty(result.Messages); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var payloadSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(payloadSymbol); - - var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool - ? field.AsNullableNumber()?.ToString() - : field?.AsString()) - .ToList(); - Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]); - } - } - - [Fact] - public void CanCanonicalizeName() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(warningsAsErrors: false, new[] - { - "build", - Path.Combine(folder, "CanonicalizeName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var payloadSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(payloadSymbol); - - var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool - ? field.AsNullableNumber()?.ToString() - : field?.AsString()) - .ToList(); - Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); - } - } - - [Fact] - public void RejectsAbsoluteName() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AbsoluteName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.InRange(result.ExitCode, 2, int.MaxValue); - - var expectedIllegalRelativeLongFileName = 1; - var expectedPayloadMustBeRelativeToCache = 2; - Assert.Equal(expectedIllegalRelativeLongFileName, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count()); - Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); - } - } - - [Fact] - public void RejectsPayloadSharedBetweenPackageAndBA() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode); - } - } - - [Fact] - public void ReplacesDownloadUrlPlaceholders() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), - Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributesByElementName = new Dictionary> - { - { "Container", new List { "FileSize", "Hash" } }, - { "Payload", new List { "FileSize", "Hash" } }, - }; - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - "", - "", - @"", - @"", - }, payloads); - - var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, containers); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs deleted file mode 100644 index ae8a1bcc..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ /dev/null @@ -1,181 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class PreprocessorFixture - { - [Fact] - public void PreprocessDirectly() - { - var folder = TestData.Get(@"TestData\IncludePath"); - var sourcePath = Path.Combine(folder, "Package.wxs"); - var includeFolder = Path.Combine(folder, "data"); - var includeFile = Path.Combine(includeFolder, "Package.wxi"); - - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - - var context = serviceProvider.GetService(); - context.SourcePath = sourcePath; - context.IncludeSearchPaths = new[] { includeFolder }; - - var preprocessor = serviceProvider.GetService(); - var result = preprocessor.Preprocess(context); - - var includedFile = result.IncludedFiles.Single(); - Assert.NotNull(result.Document); - Assert.Equal(includeFile, includedFile.Path); - Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName); - Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value); - Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName); - Assert.Null(includedFile.SourceLineNumbers.Parent); - } - - [Fact] - public void IncludeSourceLineNumbersPreserved() - { - var folder = TestData.Get(@"TestData\IncludePath"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(warningsAsErrors: false, new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-includepath", Path.Combine(folder, "data"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) - { - var intermediate = Intermediate.Load(output); - var component = intermediate.Sections.Single().Symbols.OfType().Single(); - Assert.Equal(3, component.SourceLineNumbers.LineNumber); - Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber); - - var encoded = component.SourceLineNumbers.GetEncoded(); - var decoded = SourceLineNumber.CreateFromEncoded(encoded); - Assert.Equal(3, decoded.LineNumber); - Assert.Equal(5, decoded.Parent.LineNumber); - } - } - } - - [Fact] - /// - /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" - /// which is only defined on 64-bit Windows. - /// - public void SupportParensInEnvironmentVariables() - { - var folder = TestData.Get(@"TestData", "Preprocessor"); - - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var context = serviceProvider.GetService(); - context.SourcePath = Path.Combine(folder, "EnvParens.wxs"); - - var preprocessor = serviceProvider.GetService(); - var result = preprocessor.Preprocess(context); - Assert.NotNull(result.Document); - } - - [Fact] - public void VariableRedefinitionIsAWarning() - { - var folder = TestData.Get(@"TestData\Variables"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(warningsAsErrors: false, new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision); - Assert.Single(warning); - } - } - - [Fact] - public void ForEachLoopsWork() - { - var folder = TestData.Get(@"TestData\ForEach"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - } - } - - [Fact] - public void NonterminatedPreprocessorInstructionShowsSourceLineNumber() - { - var folder = TestData.Get(@"TestData\BadIf"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - Assert.Equal(147, result.ExitCode); - Assert.StartsWith("Found a ", result.Messages.Single().ToString()); - } - } - } -} - diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs deleted file mode 100644 index e4d95b5d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs +++ /dev/null @@ -1,173 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class RegistryFixture - { - [Fact] - public void PopulatesRegistryTableFromRegistryValue() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryValue.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", - "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", - }, results); - } - } - - [Fact] - public void PopulatesRegistryTableFromRegistryValueMultiString() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", - "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", - }, results); - } - } - - [Fact] - public void DuplicateRegistryValueIdsAreDetectedSmoothly() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }, out var messages); - - Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count()); - Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count()); - } - } - - [Fact] - public void PopulatesRegistryTableFromRemoveRegistryKey() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", - }, results); - } - } - - [Fact] - public void PopulatesRegistryTableWithoutExtraBackslash() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent", - "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs deleted file mode 100644 index 9e19abb0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs +++ /dev/null @@ -1,41 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class RollbackBoundaryFixture - { - [Fact] - public void CanStartChainWithRollbackBoundary() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs deleted file mode 100644 index 3b6c50c0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ShortcutFixture - { - [Fact] - public void CanBuildShortcutNameWithShortname() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" }); - WixAssert.CompareLineByLine(new[] - { - "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", - }, results); - } - } - - [Fact] - public void PopulatesMsiShortcutPropertyTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); - WixAssert.CompareLineByLine(new[] - { - "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", - "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs deleted file mode 100644 index 15276b18..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs +++ /dev/null @@ -1,100 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using System.Xml.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class SoftwareTagFixture - { - private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; - private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; - - [Fact] - public void CanBuildPackageWithTag() - { - var folder = TestData.Get(@"TestData\ProductTag"); - var build = new Builder(folder, null, new[] { folder }); - - var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); - - var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; - var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); - results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); - WixAssert.CompareLineByLine(new[] - { - "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", - "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", - "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" - }, results.ToArray()); - } - - [Fact] - public void CanBuildBundleWithTag() - { - var testDataFolder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), - Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), - "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), - "-bindpath", Path.Combine(testDataFolder, "ProductTag"), - "-intermediateFolder", Path.Combine(intermediateFolder, "package"), - "-o", Path.Combine(baseFolder, "package", @"test.msi") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), - "-bindpath", Path.Combine(testDataFolder, "BundleTag"), - "-bindpath", Path.Combine(baseFolder, "package"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - - using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) - { - var badata = ouput.GetDataStream("wix-burndata.xml"); - var doc = XDocument.Load(badata); - - var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; - - var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); - File.WriteAllText(swidTagPath, swidTag); - - var docTag = XDocument.Load(swidTagPath); - var title = docTag.Root.Attribute("name").Value; - var version = docTag.Root.Attribute("version").Value; - Assert.Equal("~TagTestBundle", title); - Assert.Equal("4.3.2.1", version); - } - } - } - - private static void Build(string[] args) - { - var result = WixRunner.Execute(args) - .AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe b/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe deleted file mode 100644 index 2a4f423f..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs deleted file mode 100644 index b34c547d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs deleted file mode 100644 index 4dd701f0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs deleted file mode 100644 index 6b9fe013..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs deleted file mode 100644 index e255c83d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs deleted file mode 100644 index c17d9848..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi deleted file mode 100644 index ea1296c3..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs deleted file mode 100644 index f800264d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs deleted file mode 100644 index 8be5abb2..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs deleted file mode 100644 index c345305d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs deleted file mode 100644 index e0c84c63..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs deleted file mode 100644 index 45cc7114..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe deleted file mode 100644 index 18129b73..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest deleted file mode 100644 index 0da1f6d0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs deleted file mode 100644 index 3caa20ff..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs deleted file mode 100644 index 1d7ebb94..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs deleted file mode 100644 index 2a75e3d7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs deleted file mode 100644 index a2d49b18..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs deleted file mode 100644 index 0c350042..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs deleted file mode 100644 index 4fe7e097..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs deleted file mode 100644 index 5ebe5472..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs deleted file mode 100644 index 78f3ebd3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs deleted file mode 100644 index c717680b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs deleted file mode 100644 index fc53c4a2..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs deleted file mode 100644 index 6cf8528e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs deleted file mode 100644 index c3528a67..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt deleted file mode 100644 index 3b862323..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs deleted file mode 100644 index 5b41e807..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs deleted file mode 100644 index 7f5ea456..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs deleted file mode 100644 index e52302d4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs deleted file mode 100644 index eefae822..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs deleted file mode 100644 index fd8d3698..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs deleted file mode 100644 index c5a93eb3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs deleted file mode 100644 index 7303a05a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs deleted file mode 100644 index f44fb7bc..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll deleted file mode 100644 index 64061ea0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is fakeba.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs deleted file mode 100644 index 78e754c1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs deleted file mode 100644 index 18cdfd32..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs deleted file mode 100644 index a93b23ef..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs deleted file mode 100644 index e738b407..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs deleted file mode 100644 index b0bde4f6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs deleted file mode 100644 index 514f9243..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs deleted file mode 100644 index c0dc9bc0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi deleted file mode 100644 index 2cd10f09..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs deleted file mode 100644 index 15a9a0ce..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs deleted file mode 100644 index db07af2c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs deleted file mode 100644 index 7f17b538..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt deleted file mode 100644 index 8c874ae7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt +++ /dev/null @@ -1 +0,0 @@ -This is other.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs deleted file mode 100644 index a0e921cb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs deleted file mode 100644 index d7b5bdc0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs deleted file mode 100644 index beaf70bf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs deleted file mode 100644 index e175a18f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs deleted file mode 100644 index 28900e55..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs deleted file mode 100644 index 90d66cc3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs deleted file mode 100644 index be991c65..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs deleted file mode 100644 index c64ef143..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs deleted file mode 100644 index ff8741cf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs deleted file mode 100644 index f8ce1c38..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs deleted file mode 100644 index 10c4f91f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs deleted file mode 100644 index d7d86008..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs deleted file mode 100644 index d32e808c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs deleted file mode 100644 index 08a9c470..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl deleted file mode 100644 index bc2ccf04..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - This is row one - This is row two - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs deleted file mode 100644 index e1da74f8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt deleted file mode 100644 index 97f701ce..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt +++ /dev/null @@ -1 +0,0 @@ -This is file1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt deleted file mode 100644 index 46493186..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt +++ /dev/null @@ -1 +0,0 @@ -This is file2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs deleted file mode 100644 index 71553e2a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab deleted file mode 100644 index 125eeb2c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi deleted file mode 100644 index 81335041..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs deleted file mode 100644 index 246bcafc..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab deleted file mode 100644 index 125eeb2c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi deleted file mode 100644 index 9cb6d6bc..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs deleted file mode 100644 index 81915759..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab deleted file mode 100644 index 125eeb2c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi deleted file mode 100644 index 762b136c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs deleted file mode 100644 index 7c5fe3cf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm deleted file mode 100644 index 2a7b5e3a..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs deleted file mode 100644 index 2f277956..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs deleted file mode 100644 index 6df8a7c0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs deleted file mode 100644 index 4d188d3a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs deleted file mode 100644 index 9c3a9690..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs deleted file mode 100644 index ec6e62df..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs deleted file mode 100644 index 3e7887c4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs deleted file mode 100644 index 6e9a4495..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs deleted file mode 100644 index 50cf6850..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs deleted file mode 100644 index cc87b49f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs deleted file mode 100644 index a58b68c8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs deleted file mode 100644 index 01767abb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs deleted file mode 100644 index de9744a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl deleted file mode 100644 index 066e16bb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl +++ /dev/null @@ -1,9 +0,0 @@ - - - - - A newer version of [ProductName] is already installed. - MsiPackage - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs deleted file mode 100644 index 287085e8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs deleted file mode 100644 index 88a4ac81..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs deleted file mode 100644 index 5c84f33e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs deleted file mode 100644 index 7f17b538..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs deleted file mode 100644 index e57180f7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs deleted file mode 100644 index 0b094860..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs deleted file mode 100644 index be302720..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs deleted file mode 100644 index 6fb9ef05..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs deleted file mode 100644 index 6ac48963..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs deleted file mode 100644 index 8fff563e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs deleted file mode 100644 index 2a75e3d7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs deleted file mode 100644 index 1de84e81..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs deleted file mode 100644 index 0bd80c50..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs deleted file mode 100644 index 7a0485ed..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi deleted file mode 100644 index 03885e3e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi deleted file mode 100644 index f2df3b86..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs deleted file mode 100644 index 7826d673..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl deleted file mode 100644 index f7453566..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl deleted file mode 100644 index ef287da7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl deleted file mode 100644 index 10ebf2c5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs deleted file mode 100644 index 13c79e90..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl deleted file mode 100644 index 596ee077..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs deleted file mode 100644 index dfae2157..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs deleted file mode 100644 index 4fd3493a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs deleted file mode 100644 index e7492db4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt deleted file mode 100644 index ad9cdcb5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt +++ /dev/null @@ -1 +0,0 @@ -This is a1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt deleted file mode 100644 index d5de23de..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt +++ /dev/null @@ -1 +0,0 @@ -This is a2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt deleted file mode 100644 index 88bc4a56..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt +++ /dev/null @@ -1 +0,0 @@ -This is b1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt deleted file mode 100644 index 38525276..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt +++ /dev/null @@ -1 +0,0 @@ -This is b2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs deleted file mode 100644 index e6527a36..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs deleted file mode 100644 index f1c939db..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs deleted file mode 100644 index dbca3393..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll deleted file mode 100644 index b3cf17d8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is a fake BA DLL diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu deleted file mode 100644 index d63da4be..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu +++ /dev/null @@ -1 +0,0 @@ -This is a fake MSU package diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs deleted file mode 100644 index 2b1a1a0f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs deleted file mode 100644 index 82797ebe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs deleted file mode 100644 index 0bf0e963..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs deleted file mode 100644 index 5e1b99ff..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs deleted file mode 100644 index f220d81a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs deleted file mode 100644 index 149870a4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs deleted file mode 100644 index 3c361c49..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs deleted file mode 100644 index 8e62f660..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs deleted file mode 100644 index f79da874..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs deleted file mode 100644 index dda306cf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt deleted file mode 100644 index 6fd385bd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt deleted file mode 100644 index b1f0bc01..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia A v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt deleted file mode 100644 index ece55fec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is B v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt deleted file mode 100644 index cf3372fd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia B v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs deleted file mode 100644 index c9dcdd72..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs deleted file mode 100644 index d39170c0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs deleted file mode 100644 index 5cb8ede8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs deleted file mode 100644 index 52e87f64..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt deleted file mode 100644 index 6fd385bd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt +++ /dev/null @@ -1 +0,0 @@ -This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs deleted file mode 100644 index dab959d5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs deleted file mode 100644 index 889b1220..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs deleted file mode 100644 index 4a8f5630..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs deleted file mode 100644 index 7fb3cb56..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs deleted file mode 100644 index 201d177b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs deleted file mode 100644 index 62a89af3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs deleted file mode 100644 index 1b01774c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs deleted file mode 100644 index f0630ead..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs deleted file mode 100644 index f9d2a55a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt deleted file mode 100644 index 6fd385bd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt deleted file mode 100644 index b1f0bc01..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia A v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt deleted file mode 100644 index ece55fec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is B v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt deleted file mode 100644 index cf3372fd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia B v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs deleted file mode 100644 index bc460636..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs deleted file mode 100644 index e3845382..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs deleted file mode 100644 index 52e87f64..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs deleted file mode 100644 index dc94d688..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs deleted file mode 100644 index 544b80ec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs deleted file mode 100644 index f8f38ea6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs deleted file mode 100644 index 5263cbd4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs deleted file mode 100644 index 9c37a27d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs deleted file mode 100644 index 68d115c5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs deleted file mode 100644 index 37a2c462..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs deleted file mode 100644 index 5bf78a9d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs deleted file mode 100644 index f62bbd0e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs deleted file mode 100644 index 433be7f0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs deleted file mode 100644 index 0621eb8d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs deleted file mode 100644 index d3b31db5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs deleted file mode 100644 index 5166be16..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs deleted file mode 100644 index 8f4f661d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs deleted file mode 100644 index 452aea69..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs deleted file mode 100644 index 1fb2e906..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs deleted file mode 100644 index fe6e179e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs deleted file mode 100644 index c62c571d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs deleted file mode 100644 index a55a1e18..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs deleted file mode 100644 index 3218295b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs deleted file mode 100644 index ecfccfcb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs deleted file mode 100644 index bbad63e6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt deleted file mode 100644 index 1970cae6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt deleted file mode 100644 index fa2c7082..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is b\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt deleted file mode 100644 index 1c0cbda6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is c\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs deleted file mode 100644 index d5379e7b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi deleted file mode 100644 index 7f894091..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs deleted file mode 100644 index 65cba20e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs deleted file mode 100644 index d3f8accf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs deleted file mode 100644 index 7e8f2e99..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs deleted file mode 100644 index f16fce0d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs deleted file mode 100644 index da1e4f38..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs deleted file mode 100644 index 27f2ab9b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs deleted file mode 100644 index d704bbf1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi deleted file mode 100644 index 8737f3c2..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl deleted file mode 100644 index bc1dee83..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - ~TestBundle - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs deleted file mode 100644 index 21749c07..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs deleted file mode 100644 index f5fe9885..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs deleted file mode 100644 index 48f53ae3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll deleted file mode 100644 index 0e461ba8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll +++ /dev/null @@ -1 +0,0 @@ -This is Shared.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt deleted file mode 100644 index 8b986220..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll deleted file mode 100644 index 970efdf0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is a fakeba.dll \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi deleted file mode 100644 index 0722d60e..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm deleted file mode 100644 index 6f179aba..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs deleted file mode 100644 index 3c999812..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl deleted file mode 100644 index c74e86a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - Example Company - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj deleted file mode 100644 index 597d4318..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - Debug - x86 - 0.9 - 27df04c6-3cef-4b9a-bac6-4e78d188384f - MergeModule1 - Module - MergeModule1 - MergeModule1 - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - Debug - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - Debug - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - - - - - - - - - - FgwepExtension.wixext - $(WixExtDir)\FgwepExtension.wixext.dll - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs deleted file mode 100644 index 8317e7af..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs deleted file mode 100644 index cad1f049..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs deleted file mode 100644 index 0d459f02..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs deleted file mode 100644 index d7b5bdc0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs deleted file mode 100644 index b8e9f59c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs deleted file mode 100644 index baa0c6b1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl deleted file mode 100644 index c74e86a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - Example Company - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs deleted file mode 100644 index f4ce9c48..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs deleted file mode 100644 index 669de6ec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl deleted file mode 100644 index 77d46861..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - Tahoma - 8 - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs deleted file mode 100644 index a591fdd9..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs deleted file mode 100644 index fa64f98f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs deleted file mode 100644 index 587d8e95..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs deleted file mode 100644 index 59839f30..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs deleted file mode 100644 index 7e459e9a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs deleted file mode 100644 index 7de55810..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - -= 4 AND $(sys.WIXMAJORVERSION) < 5 ?> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs deleted file mode 100644 index f8203a07..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs deleted file mode 100644 index df867923..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt deleted file mode 100644 index eab3a9b5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt +++ /dev/null @@ -1 +0,0 @@ -This is test2.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs deleted file mode 100644 index 7e6eee9f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs deleted file mode 100644 index b29a785f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs deleted file mode 100644 index 7d1a4ae1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll deleted file mode 100644 index fd36c768..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll +++ /dev/null @@ -1 +0,0 @@ -This is alpha\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll deleted file mode 100644 index 292925c7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll +++ /dev/null @@ -1 +0,0 @@ -This is mips\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll deleted file mode 100644 index 663e9d99..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll +++ /dev/null @@ -1 +0,0 @@ -This is powerpc\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs deleted file mode 100644 index 5330305e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using WixToolset.Core.TestPackage; - using Xunit; - - public class TestXmlFixture - { - [Fact] - public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() - { - var original = @" - - - - -"; - var expected = ""; - var ignored = new Dictionary> { { "Target", new List { "One", "Two", "Missing" } } }; - Assert.Equal(expected, original.GetTestXml(ignored)); - } - - [Fact] - public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() - { - var original = ""; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - - [Fact] - public void RemovesAllNamespacesToReduceTyping() - { - var original = ""; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - - [Fact] - public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() - { - var original = @" - - - - -"; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - - [Fact] - public void RemovesXmlDeclarationToReduceTyping() - { - var original = ""; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs deleted file mode 100644 index 15e5d334..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs +++ /dev/null @@ -1,75 +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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using WixToolset.Core; - using WixToolset.Data; - using WixToolset.Data.Bind; - using WixToolset.Extensibility.Services; - using Xunit; - - public class VariableResolverFixture - { - [Fact] - public void CanRecursivelyResolveVariables() - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var variableResolver = serviceProvider.GetService(); - - var variables = new Dictionary() - { - { "ProductName", new BindVariable() { Id = "ProductName", Value = "Localized Product Name" } }, - { "ProductNameEdition", new BindVariable() { Id = "ProductNameEdition", Value = "!(loc.ProductName) Enterprise Edition" } }, - { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, - }; - - var localization = new Localization(0, null, "x-none", variables, new Dictionary()); - - variableResolver.AddLocalization(localization); - - var result = variableResolver.ResolveVariables(null, "These are not the loc strings you're looking for."); - Assert.Equal("These are not the loc strings you're looking for.", result.Value); - Assert.False(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductName)"); - Assert.Equal("Welcome to Localized Product Name", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEdition)"); - Assert.Equal("Welcome to Localized Product Name Enterprise Edition", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion)"); - Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(bind.property.ProductVersion)"); - Assert.Equal("Welcome to !(bind.property.ProductVersion)", result.Value); - Assert.False(result.UpdatedValue); - Assert.True(result.DelayedResolve); - - var withUnknownLocString = "Welcome to !(loc.UnknownLocalizationVariable)"; - Assert.Throws(() => variableResolver.ResolveVariables(null, withUnknownLocString)); - - result = variableResolver.ResolveVariables(null, withUnknownLocString, errorOnUnknown: false); - Assert.Equal(withUnknownLocString, result.Value); - Assert.False(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable)"); - Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable)", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); - Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); - Assert.True(result.UpdatedValue); - Assert.True(result.DelayedResolve); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion) !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); - Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3 !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); - Assert.True(result.UpdatedValue); - Assert.True(result.DelayedResolve); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs deleted file mode 100644 index c5b6c261..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs +++ /dev/null @@ -1,63 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class WarningFixture - { - [Fact] - public void SuppressedWarningsWithWarningAsErrorsAreNotErrors() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(warningsAsErrors: true, new[] - { - "build", - "-sw1152", - Path.Combine(folder, "CanonicalizeName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - } - } - - [Fact] - public void WarningsAsErrorsTreatsWarningsAsErrors() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(warningsAsErrors: true, new[] - { - "build", - Path.Combine(folder, "CanonicalizeName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal((int)WarningMessages.Ids.PathCanonicalized, result.ExitCode); - - var message = Assert.Single(result.Messages); - Assert.Equal(MessageLevel.Warning, message.Level); // TODO: is this right? - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj deleted file mode 100644 index fc62e932..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - netcoreapp3.1 - false - embedded - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs deleted file mode 100644 index 942f253f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs +++ /dev/null @@ -1,205 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Example.Extension; - using Xunit; - - public class WixiplFixture - { - [Fact] - public void CanBuildSingleFile() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixiplPath = Path.Combine(intermediateFolder, @"test.wixipl"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixiplPath, - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixiplPath); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.False(intermediate.HasLevel(IntermediateLevels.Resolved)); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(intermediateFolder, @"test.wixipl"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); - - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CannotBuildWithSourceFileAndWixipl() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixipl") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(intermediateFolder, @"test.wixipl"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode); - } - } - - [Fact] - public void CanBuildMsiUsingExtensionLibrary() - { - var folder = TestData.Get(@"TestData\Wixipl"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - "-ext", extensionPath, - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi"), - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - { - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - - { - var binary = section.Symbols.OfType().Single(); - var path = binary[BinarySymbolFields.Data].AsPath().Path; - Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path); - Assert.EndsWith(@"wix-ir\example.txt", path); - Assert.Equal(@"BinFromWir", binary.Id.Id); - } - } - } - - [Fact] - public void CanBuildWixiplUsingExtensionLibrary() - { - var folder = TestData.Get(@"TestData\Wixipl"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - "-ext", extensionPath, - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixipl"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(intermediateFolder, @"test.wixipl"), - "-ext", extensionPath, - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi"), - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - { - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - - { - var binary = section.Symbols.OfType().Single(); - var path = binary[BinarySymbolFields.Data].AsPath().Path; - Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path); - Assert.EndsWith(@"wix-ir\example.txt", path); - Assert.Equal(@"BinFromWir", binary.Id.Id); - } - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs deleted file mode 100644 index d7296cfe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ /dev/null @@ -1,316 +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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class WixlibFixture - { - [Fact] - public void CanBuildSimpleBundleUsingWixlib() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildWixlibWithBinariesFromNamedBindPaths() - { - var folder = TestData.Get(@"TestData\WixlibWithBinaries"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - // Use names that aren't excluded in default .gitignores. - "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}", - "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var wixlib = Intermediate.Load(wixlibPath); - var binarySymbols = wixlib.Sections.SelectMany(s => s.Symbols).OfType().ToList(); - Assert.Equal(3, binarySymbols.Count); - Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll")); - Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-1")); - Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-2")); - } - } - - [Fact] - public void CanBuildSingleFileUsingWixlib() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var wixlib = Intermediate.Load(wixlibPath); - - Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); - Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); - - var section = intermediate.Sections.Single(); - - var wixFile = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", wixFile[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanOverridePathWixVariable() - { - var folder = TestData.Get(@"TestData\WixVariableOverride"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var wixlib = Intermediate.Load(wixlibPath); - - Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); - Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); - - var section = intermediate.Sections.Single(); - - var wixFile = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path); - } - } - - [Fact] - public void CanBuildWithExtensionUsingWixlib() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-ext", extensionPath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); - Assert.Equal("Foo", example.Id?.Id); - Assert.Equal("Bar", example[0].AsString()); - } - } - - [Fact] - public void CanBuildWithExtensionUsingMultipleWixlibs() - { - var folder = TestData.Get(@"TestData\ComplexExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-ext", extensionPath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"components.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "OtherComponents.wxs"), - "-ext", extensionPath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"other.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"components.wixlib"), - "-lib", Path.Combine(intermediateFolder, @"other.wixlib"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbols = section.Symbols.OfType().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray(); - Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbols[0][FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"example.txt", fileSymbols[0][FileSymbolFields.Source].PreviousValue.AsPath().Path); - Assert.Equal(Path.Combine(folder, @"data\other.txt"), fileSymbols[1][FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"other.txt", fileSymbols[1][FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray(); - Assert.Equal(new string[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray()); - Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[0].AsString()).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs deleted file mode 100644 index 57351b27..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs +++ /dev/null @@ -1,81 +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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class WixlibQueryFixture - { - [Fact] - public void UpgradeProducesReferenceToRemoveExistingProducts() - { - var folder = TestData.Get(@"TestData\Upgrade"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DetectOnly.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var wixSimpleRefSymbols = allSymbols.OfType(); - var repRef = wixSimpleRefSymbols.Where(t => t.Table == "WixAction" && - t.PrimaryKeys == "InstallExecuteSequence/RemoveExistingProducts") - .SingleOrDefault(); - Assert.NotNull(repRef); - } - } - - [Fact] - public void TypeLibLanguageAsStringReturnsZero() - { - var folder = TestData.Get(@"TestData\TypeLib"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Language0.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var typeLibSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(typeLibSymbol); - - var fields = typeLibSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool - ? field.AsNullableNumber()?.ToString() - : field?.AsString()) - .ToList(); - Assert.Equal("0", fields[1]); - } - } - } -} diff --git a/src/version.json b/src/version.json new file mode 100644 index 00000000..5f857771 --- /dev/null +++ b/src/version.json @@ -0,0 +1,11 @@ +{ + "version": "4.0", + "publicReleaseRefSpec": [ + "^refs/heads/master$" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + } +} diff --git a/src/wix/Custom.Build.props b/src/wix/Custom.Build.props new file mode 100644 index 00000000..889fb62e --- /dev/null +++ b/src/wix/Custom.Build.props @@ -0,0 +1,6 @@ + + + + true + + diff --git a/src/wix/Directory.Build.props b/src/wix/Directory.Build.props new file mode 100644 index 00000000..b3c6287c --- /dev/null +++ b/src/wix/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + + Debug + false + MSB3246 + + $(MSBuildProjectName) + $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\)) + $(BaseOutputPath)obj\$(ProjectName)\ + $(BaseOutputPath)$(Configuration)\ + + WiX Toolset Team + WiX Toolset + Copyright (c) .NET Foundation and contributors. All rights reserved. + MS-RL + WiX Toolset + + + + + diff --git a/src/wix/Directory.Build.targets b/src/wix/Directory.Build.targets new file mode 100644 index 00000000..2fcc765a --- /dev/null +++ b/src/wix/Directory.Build.targets @@ -0,0 +1,51 @@ + + + + + + + true + $(SolutionPath) + $(NCrunchOriginalSolutionPath) + + + + + + + $([System.IO.File]::ReadAllText($(TheSolutionPath))) + $([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) )) + (?<="[PackageName]", ")(.*)(?=", ") + + + + + + %(Identity) + $(SolutionFileContent.Contains('\%(Identity).csproj')) + + + + + $(RegexPattern.Replace('[PackageName]','%(PackageName)') ) + $([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)')) + + + + + + + + + + + + + + diff --git a/src/wix/Directory.csproj.props b/src/wix/Directory.csproj.props new file mode 100644 index 00000000..81d24ad1 --- /dev/null +++ b/src/wix/Directory.csproj.props @@ -0,0 +1,13 @@ + + + + + true + true + $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk)) + false + + diff --git a/src/wix/Directory.csproj.targets b/src/wix/Directory.csproj.targets new file mode 100644 index 00000000..c3270426 --- /dev/null +++ b/src/wix/Directory.csproj.targets @@ -0,0 +1,26 @@ + + + + + false + $(OutputPath)\$(AssemblyName).xml + + + + + $(PrivateRepositoryUrl.Replace('.git','')) + + $(MSBuildProjectName).nuspec + $(OutputPath)..\ + $(NuspecProperties);Id=$(PackageId);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description);Title=$(Title) + $(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl) + true + snupkg + + + + diff --git a/src/wix/README.md b/src/wix/README.md new file mode 100644 index 00000000..622cd3f9 --- /dev/null +++ b/src/wix/README.md @@ -0,0 +1,3 @@ +# Core +WixToolset.Core - preprocessor, compiler, linker and binder for Windows Installer and Burn + diff --git a/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs new file mode 100644 index 00000000..0da78797 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs @@ -0,0 +1,27 @@ +// 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.Symbols; + + internal abstract class BaseSearchFacade : ISearchFacade + { + protected WixSearchSymbol SearchSymbol { get; set; } + + public virtual void WriteXml(XmlTextWriter writer) + { + writer.WriteAttributeString("Id", this.SearchSymbol.Id.Id); + writer.WriteAttributeString("Variable", this.SearchSymbol.Variable); + if (!String.IsNullOrEmpty(this.SearchSymbol.Condition)) + { + writer.WriteAttributeString("Condition", this.SearchSymbol.Condition); + } + if (!String.IsNullOrEmpty(this.SearchSymbol.BundleExtensionRef)) + { + writer.WriteAttributeString("ExtensionId", this.SearchSymbol.BundleExtensionRef); + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..4a4f06f3 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -0,0 +1,650 @@ +// 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 WixToolset.Core.Burn.Bind; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Binds a this.bundle. + /// + internal class BindBundleCommand + { + public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) + { + this.ServiceProvider = context.ServiceProvider; + + this.Messaging = context.ServiceProvider.GetService(); + + this.BackendHelper = context.ServiceProvider.GetService(); + this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); + this.PayloadHarvester = context.ServiceProvider.GetService(); + + this.DefaultCompressionLevel = context.DefaultCompressionLevel; + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + this.IntermediateFolder = context.IntermediateFolder; + this.Output = context.IntermediateRepresentation; + this.OutputPath = context.OutputPath; + this.OutputPdbPath = context.PdbPath; + //this.VariableResolver = context.VariableResolver; + + this.BackendExtensions = backedExtensions; + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + + private IPayloadHarvester PayloadHarvester { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + private IEnumerable BackendExtensions { get; } + + private Intermediate Output { get; } + + private string OutputPath { get; } + + private string OutputPdbPath { get; } + + private string IntermediateFolder { get; } + + private IVariableResolver VariableResolver { get; } + + public IReadOnlyCollection FileTransfers { get; private set; } + + public IReadOnlyCollection TrackedFiles { get; private set; } + + public WixOutput Wixout { get; private set; } + + public void Execute() + { + var section = this.Output.Sections.Single(); + + var fileTransfers = new List(); + var trackedFiles = new List(); + + // 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! + var chainPackageSymbols = this.GetRequiredSymbols(); + + var wixGroupSymbols = this.GetRequiredSymbols(); + + // Ensure there is one and only one WixBundleSymbol. + // The compiler and linker behavior should have colluded to get + // this behavior. + var bundleSymbol = this.GetSingleSymbol(); + + bundleSymbol.ProviderKey = bundleSymbol.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant(); + + bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. + + // Ensure there is one and only one WixBootstrapperApplicationDllSymbol. + // The compiler and linker behavior should have colluded to get + // this behavior. + var bundleApplicationDllSymbol = this.GetSingleSymbol(); + + // Ensure there is one and only one WixChainSymbol. + // The compiler and linker behavior should have colluded to get + // this behavior. + var chainSymbol = this.GetSingleSymbol(); + + if (this.Messaging.EncounteredError) + { + return; + } + + // If there are any fields to resolve later, create the cache to populate during bind. + var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; + + IEnumerable orderedSearches; + IDictionary> extensionSearchSymbolsById; + { + var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); + orderSearchesCommand.Execute(); + + orderedSearches = orderSearchesCommand.OrderedSearchFacades; + extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; + } + + // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). + { + var extractedFiles = this.BackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); + + trackedFiles.AddRange(extractedFiles); + } + + // Get the explicit payloads. + var payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); + + var layoutDirectory = Path.GetDirectoryName(this.OutputPath); + + // Process the explicitly authored payloads. + ISet processedPayloads; + { + var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + + processedPayloads = new HashSet(payloadSymbols.Keys); + } + + IDictionary facades; + { + var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section); + command.Execute(); + + facades = command.PackageFacades; + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Process each package facade. Note this is likely to add payloads and other symbols so + // note that any indexes created above may be out of date now. + foreach (var facade in facades.Values) + { + switch (facade.PackageSymbol.Type) + { + case WixBundlePackageType.Exe: + { + var command = new ProcessExePackageCommand(facade, payloadSymbols); + command.Execute(); + } + break; + + case WixBundlePackageType.Msi: + { + var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]); + command.Execute(); + } + break; + + case WixBundlePackageType.Msp: + { + var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadSymbols); + command.Execute(); + } + break; + + case WixBundlePackageType.Msu: + { + var command = new ProcessMsuPackageCommand(facade, payloadSymbols); + command.Execute(); + } + break; + } + + if (null != variableCache) + { + BindBundleCommand.PopulatePackageVariableCache(facade, variableCache); + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) + // are present. + payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + wixGroupSymbols = this.GetRequiredSymbols(); + packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); + + // Process the payloads that were added by processing the packages. + { + var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); + + var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + + processedPayloads = null; + } + + // Set the package metadata from the payloads now that we have the complete payload information. + { + foreach (var facade in facades.Values) + { + facade.PackageSymbol.Size = 0; + + var packagePayloads = packagesPayloads[facade.PackageId]; + + foreach (var payload in packagePayloads.Values) + { + facade.PackageSymbol.Size += payload.FileSize.Value; + } + + if (!facade.PackageSymbol.InstallSize.HasValue) + { + facade.PackageSymbol.InstallSize = facade.PackageSymbol.Size; + } + + var packagePayload = payloadSymbols[facade.PackageSymbol.PayloadRef]; + + if (String.IsNullOrEmpty(facade.PackageSymbol.Description)) + { + facade.PackageSymbol.Description = packagePayload.Description; + } + + if (String.IsNullOrEmpty(facade.PackageSymbol.DisplayName)) + { + facade.PackageSymbol.DisplayName = packagePayload.DisplayName; + } + } + } + + // Give the UX payloads their embedded IDs... + var uxPayloadIndex = 0; + { + foreach (var payload in payloadSymbols.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef)) + { + // 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) + { + this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path)); + 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(ErrorMessages.MissingBundleInformation("BootstrapperApplication")); + } + + // Give the embedded payloads without an embedded id yet an embedded id. + var payloadIndex = 0; + foreach (var payload in payloadSymbols.Values) + { + Debug.Assert(PackagingType.Unknown != payload.Packaging); + + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) + { + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex); + ++payloadIndex; + } + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Determine patches to automatically slipstream. + { + var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + IEnumerable orderedFacades; + IEnumerable boundaries; + { + var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, section, facades); + command.Execute(); + + orderedFacades = command.OrderedPackageFacades; + boundaries = command.UsedRollbackBoundaries; + } + + // Resolve any delayed fields before generating the manifest. + if (this.DelayedFields.Any()) + { + this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); + } + + { + var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); + command.Execute(); + + if (!String.IsNullOrEmpty(command.BundleProviderKey)) + { + bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. + } + } + + // Update the bundle per-machine/per-user scope based on the chained packages. + this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); + + var softwareTags = section.Symbols.OfType().ToList(); + if (softwareTags.Any()) + { + var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); + command.Execute(); + } + + this.DetectDuplicateCacheIds(facades); + + if (this.Messaging.EncounteredError) + { + return; + } + + // Give the extension one last hook before generating the output files. + foreach (var extension in this.BackendExtensions) + { + extension.SymbolsFinalized(section); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Generate data for all manifests. + { + var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Generate the core-defined BA manifest tables... + string baManifestPath; + { + var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper); + command.Execute(); + + var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; + baManifestPath = command.OutputPath; + payloadSymbols.Add(baManifestPayload.Id.Id, baManifestPayload); + ++uxPayloadIndex; + + trackedFiles.Add(this.BackendHelper.TrackFile(baManifestPath, TrackedFileType.Temporary)); + } + + // Generate the bundle extension manifest... + string bextManifestPath; + { + var command = new CreateBundleExtensionManifestCommand(section, bundleSymbol, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper); + command.Execute(); + + var bextManifestPayload = command.BundleExtensionManifestPayloadRow; + bextManifestPath = command.OutputPath; + payloadSymbols.Add(bextManifestPayload.Id.Id, bextManifestPayload); + ++uxPayloadIndex; + + trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary)); + } + + var containers = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + { + var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // 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. + WixBundleContainerSymbol uxContainer; + IEnumerable uxPayloads; + { + var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + + uxContainer = command.UXContainer; + uxPayloads = command.UXContainerPayloads; + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Resolve the download URLs now that we have all of the containers and payloads calculated. + { + var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols); + command.Execute(); + } + + // Create the bundle manifest. + string manifestPath; + { + var executableName = Path.GetFileName(this.OutputPath); + + var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); + command.Execute(); + + manifestPath = command.OutputPath; + trackedFiles.Add(this.BackendHelper.TrackFile(manifestPath, TrackedFileType.Temporary)); + } + + // Create the UX container. + { + var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + uxContainer.Hash = command.Hash; + uxContainer.Size = command.Size; + + trackedFiles.Add(this.BackendHelper.TrackFile(uxContainer.WorkingPath, TrackedFileType.Temporary)); + } + + { + var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); + command.Execute(); + + fileTransfers.Add(command.Transfer); + trackedFiles.Add(this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final)); + } + +#if TODO // does this need to come back, or do they only need to be in TrackedFiles? + this.ContentFilePaths = payloadSymbols.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); +#endif + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + this.Wixout = this.CreateWixout(trackedFiles, this.Output, manifestPath, baManifestPath, bextManifestPath); + } + + private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, string manifestPath, string baDataPath, string bextDataPath) + { + WixOutput wixout; + + if (String.IsNullOrEmpty(this.OutputPdbPath)) + { + wixout = WixOutput.Create(); + } + else + { + var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); + trackedFiles.Add(trackPdb); + + wixout = WixOutput.Create(trackPdb.Path); + } + + intermediate.Save(wixout); + + wixout.ImportDataStream(BurnConstants.BurnManifestWixOutputStreamName, manifestPath); + wixout.ImportDataStream(BurnConstants.BootstrapperApplicationDataWixOutputStreamName, baDataPath); + wixout.ImportDataStream(BurnConstants.BundleExtensionDataWixOutputStreamName, bextDataPath); + + wixout.Reopen(); + + return wixout; + } + + /// + /// Populates the variable cache with specific package properties. + /// + /// The package facade with properties to cache. + /// The property cache. + private static void PopulatePackageVariableCache(PackageFacade facade, IDictionary variableCache) + { + var package = facade.PackageSymbol; + var id = package.Id.Id; + + variableCache.Add(String.Concat("packageDescription.", id), package.Description ?? String.Empty); + variableCache.Add(String.Concat("packageName.", id), package.DisplayName ?? String.Empty); + variableCache.Add(String.Concat("packageVersion.", id), package.Version); + + if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + variableCache.Add(String.Concat("packageLanguage.", id), msiPackage.ProductLanguage.ToString()); + variableCache.Add(String.Concat("packageManufacturer.", id), msiPackage.Manufacturer ?? String.Empty); + } + else + { + variableCache.Add(String.Concat("packageLanguage.", id), String.Empty); + variableCache.Add(String.Concat("packageManufacturer.", id), String.Empty); + } + } + + private void ResolveBundleInstallScope(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable facades) + { + var dependencySymbolsById = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + foreach (var facade in facades) + { + if (bundleSymbol.PerMachine && YesNoDefaultType.No == facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); + + bundleSymbol.Attributes &= ~WixBundleAttributes.PerMachine; + break; + } + } + + foreach (var facade in facades) + { + // Update package scope from bundle scope if default. + if (YesNoDefaultType.Default == facade.PackageSymbol.PerMachine) + { + facade.PackageSymbol.PerMachine = bundleSymbol.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 (!bundleSymbol.PerMachine && + YesNoDefaultType.Yes == facade.PackageSymbol.PerMachine && + !facade.PackageSymbol.Permanent && + dependencySymbolsById.ContainsKey(facade.PackageId)) + { + this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); + } + } + } + + private void DetectDuplicateCacheIds(IDictionary facades) + { + var duplicateCacheIdDetector = new Dictionary(); + + foreach (var facade in facades.Values) + { + if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage)) + { + this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId, facade.PackageId)); + this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(collisionPackage.SourceLineNumbers)); + } + else + { + duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol); + } + } + } + + private IEnumerable GetRequiredSymbols() where T : IntermediateSymbol + { + var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); + + if (0 == symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols; + } + + private T GetSingleSymbol() where T : IntermediateSymbol + { + var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); + + if (1 != symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols[0]; + } + + private static Dictionary> RecalculatePackagesPayloads(Dictionary payloadSymbols, IEnumerable wixGroupSymbols) + { + var packagesPayloads = new Dictionary>(); + + foreach (var groupSymbol in wixGroupSymbols) + { + if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) + { + var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; + + if (ComplexReferenceParentType.Package == groupSymbol.ParentType) + { + if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById)) + { + packagePayloadsById = new Dictionary(); + packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById); + } + + packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol); + } + } + } + + return packagesPayloads; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs new file mode 100644 index 00000000..773250d7 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.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.Burn +{ + using System.Xml; + using WixToolset.Data.Symbols; + + internal class ExtensionSearchFacade : BaseSearchFacade + { + public ExtensionSearchFacade(WixSearchSymbol searchSymbol) + { + this.SearchSymbol = searchSymbol; + } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("ExtensionSearch"); + + base.WriteXml(writer); + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs new file mode 100644 index 00000000..a76f84ec --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -0,0 +1,237 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.ExtensibilityServices; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class GenerateManifestDataFromIRCommand + { + public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions, IBurnBackendHelper backendHelper, IDictionary> extensionSearchSymbolsById) + { + this.Messaging = messaging; + this.Section = section; + this.BackendExtensions = backendExtensions; + this.BackendHelper = backendHelper; + this.ExtensionSearchSymbolsById = extensionSearchSymbolsById; + } + + private IEnumerable BackendExtensions { get; } + + private IBurnBackendHelper BackendHelper { get; } + + private IDictionary> ExtensionSearchSymbolsById { get; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public void Execute() + { + var symbols = this.Section.Symbols.ToList(); + var cellsByCustomDataAndElementId = new Dictionary>(); + var customDataById = new Dictionary(); + + foreach (var kvp in this.ExtensionSearchSymbolsById) + { + var extensionId = kvp.Key; + var extensionSearchSymbols = kvp.Value; + foreach (var extensionSearchSymbol in extensionSearchSymbols) + { + this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchSymbol, symbolIdIsIdAttribute: true); + symbols.Remove(extensionSearchSymbol); + } + } + + foreach (var symbol in symbols) + { + var unknownSymbol = false; + switch (symbol.Definition.Type) + { + // Symbols used internally and are not added to a data manifest. + case SymbolDefinitionType.ProvidesDependency: + case SymbolDefinitionType.WixApprovedExeForElevation: + case SymbolDefinitionType.WixBootstrapperApplication: + case SymbolDefinitionType.WixBootstrapperApplicationDll: + case SymbolDefinitionType.WixBundle: + case SymbolDefinitionType.WixBundleContainer: + case SymbolDefinitionType.WixBundleCustomDataAttribute: + case SymbolDefinitionType.WixBundleExePackage: + case SymbolDefinitionType.WixBundleExePackagePayload: + case SymbolDefinitionType.WixBundleExtension: + case SymbolDefinitionType.WixBundleMsiFeature: + case SymbolDefinitionType.WixBundleMsiPackage: + case SymbolDefinitionType.WixBundleMsiPackagePayload: + case SymbolDefinitionType.WixBundleMsiProperty: + case SymbolDefinitionType.WixBundleMspPackage: + case SymbolDefinitionType.WixBundleMspPackagePayload: + case SymbolDefinitionType.WixBundleMsuPackage: + case SymbolDefinitionType.WixBundleMsuPackagePayload: + case SymbolDefinitionType.WixBundlePackage: + case SymbolDefinitionType.WixBundlePackageCommandLine: + case SymbolDefinitionType.WixBundlePackageExitCode: + case SymbolDefinitionType.WixBundlePackageGroup: + case SymbolDefinitionType.WixBundlePatchTargetCode: + case SymbolDefinitionType.WixBundlePayload: + case SymbolDefinitionType.WixBundlePayloadGroup: + case SymbolDefinitionType.WixBundleRelatedPackage: + case SymbolDefinitionType.WixBundleRollbackBoundary: + case SymbolDefinitionType.WixBundleSlipstreamMsp: + case SymbolDefinitionType.WixBundleTag: + case SymbolDefinitionType.WixBundleUpdate: + case SymbolDefinitionType.WixBundleVariable: + case SymbolDefinitionType.WixBuildInfo: + case SymbolDefinitionType.WixChain: + case SymbolDefinitionType.WixComponentSearch: + case SymbolDefinitionType.WixDependencyProvider: + case SymbolDefinitionType.WixFileSearch: + case SymbolDefinitionType.WixGroup: + case SymbolDefinitionType.WixProductSearch: + case SymbolDefinitionType.WixRegistrySearch: + case SymbolDefinitionType.WixRelatedBundle: + case SymbolDefinitionType.WixSearch: + case SymbolDefinitionType.WixSearchRelation: + case SymbolDefinitionType.WixSetVariable: + case SymbolDefinitionType.WixUpdateRegistration: + break; + + // Symbols used before binding. + case SymbolDefinitionType.WixComplexReference: + case SymbolDefinitionType.WixOrdering: + case SymbolDefinitionType.WixSimpleReference: + case SymbolDefinitionType.WixVariable: + break; + + // Symbols to investigate: + case SymbolDefinitionType.WixChainItem: + break; + + case SymbolDefinitionType.WixBundleCustomData: + unknownSymbol = !this.IndexBundleCustomDataSymbol((WixBundleCustomDataSymbol)symbol, customDataById); + break; + + case SymbolDefinitionType.WixBundleCustomDataCell: + this.IndexBundleCustomDataCellSymbol((WixBundleCustomDataCellSymbol)symbol, cellsByCustomDataAndElementId); + break; + + case SymbolDefinitionType.MustBeFromAnExtension: + unknownSymbol = !this.AddSymbolFromExtension(symbol); + break; + + default: + unknownSymbol = true; + break; + } + + if (unknownSymbol) + { + this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); + } + } + + this.AddIndexedCellSymbols(customDataById, cellsByCustomDataAndElementId); + } + + private bool IndexBundleCustomDataSymbol(WixBundleCustomDataSymbol wixBundleCustomDataSymbol, Dictionary customDataById) + { + switch (wixBundleCustomDataSymbol.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + case WixBundleCustomDataType.BundleExtension: + break; + default: + return false; + } + + var customDataId = wixBundleCustomDataSymbol.Id.Id; + customDataById.Add(customDataId, wixBundleCustomDataSymbol); + return true; + } + + private void IndexBundleCustomDataCellSymbol(WixBundleCustomDataCellSymbol wixBundleCustomDataCellSymbol, Dictionary> cellsByCustomDataAndElementId) + { + var tableAndRowId = wixBundleCustomDataCellSymbol.CustomDataRef + "/" + wixBundleCustomDataCellSymbol.ElementId; + if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells)) + { + cells = new List(); + cellsByCustomDataAndElementId.Add(tableAndRowId, cells); + } + + cells.Add(wixBundleCustomDataCellSymbol); + } + + private void AddIndexedCellSymbols(Dictionary customDataById, Dictionary> cellsByCustomDataAndElementId) + { + foreach (var elementValues in cellsByCustomDataAndElementId.Values) + { + var elementName = elementValues[0].CustomDataRef; + var customDataSymbol = customDataById[elementName]; + + var attributeNames = customDataSymbol.AttributeNamesSeparated; + + var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value); + + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) + { + switch (customDataSymbol.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); + break; + case WixBundleCustomDataType.BundleExtension: + writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); + break; + default: + throw new NotImplementedException(); + } + + // Write all row data as attributes in table column order. + foreach (var attributeName in attributeNames) + { + if (elementValuesByAttribute.TryGetValue(attributeName, out var value)) + { + writer.WriteAttributeString(attributeName, value); + } + } + + writer.WriteEndElement(); + } + + switch (customDataSymbol.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); + break; + case WixBundleCustomDataType.BundleExtension: + this.BackendHelper.AddBundleExtensionData(customDataSymbol.BundleExtensionRef, sb.ToString()); + break; + default: + throw new NotImplementedException(); + } + } + } + + private bool AddSymbolFromExtension(IntermediateSymbol symbol) + { + foreach (var extension in this.BackendExtensions) + { + if (extension.TryProcessSymbol(this.Section, symbol)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs new file mode 100644 index 00000000..24d6f542 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs @@ -0,0 +1,185 @@ +// 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; + using WixToolset.Data.Symbols; + + internal class LegacySearchFacade : BaseSearchFacade + { + public LegacySearchFacade(WixSearchSymbol searchSymbol, IntermediateSymbol searchSpecificSymbol) + { + this.SearchSymbol = searchSymbol; + this.SearchSpecificSymbol = searchSpecificSymbol; + } + + public IntermediateSymbol SearchSpecificSymbol { get; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup a search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + switch (this.SearchSpecificSymbol) + { + case WixComponentSearchSymbol symbol: + this.WriteComponentSearchXml(writer, symbol); + break; + case WixFileSearchSymbol symbol: + this.WriteFileSearchXml(writer, symbol); + break; + case WixProductSearchSymbol symbol: + this.WriteProductSearchXml(writer, symbol); + break; + case WixRegistrySearchSymbol symbol: + this.WriteRegistrySearchXml(writer, symbol); + break; + } + } + + private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchSymbol searchSymbol) + { + writer.WriteStartElement("MsiComponentSearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("ComponentId", searchSymbol.Guid); + + if (!String.IsNullOrEmpty(searchSymbol.ProductCode)) + { + writer.WriteAttributeString("ProductCode", searchSymbol.ProductCode); + } + + if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.KeyPath)) + { + writer.WriteAttributeString("Type", "keyPath"); + } + else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.WantDirectory)) + { + writer.WriteAttributeString("Type", "directory"); + } + + writer.WriteEndElement(); + } + + private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchSymbol searchSymbol) + { + writer.WriteStartElement((0 == (searchSymbol.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("Path", searchSymbol.Path); + if (WixFileSearchAttributes.WantExists == (searchSymbol.Attributes & WixFileSearchAttributes.WantExists)) + { + writer.WriteAttributeString("Type", "exists"); + } + else if (WixFileSearchAttributes.WantVersion == (searchSymbol.Attributes & WixFileSearchAttributes.WantVersion)) + { + // Can never get here for DirectorySearch. + writer.WriteAttributeString("Type", "version"); + } + else + { + writer.WriteAttributeString("Type", "path"); + } + writer.WriteEndElement(); + } + + private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol symbol) + { + writer.WriteStartElement("MsiProductSearch"); + + base.WriteXml(writer); + + if (0 != (symbol.Attributes & WixProductSearchAttributes.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", symbol.Guid); + } + else + { + writer.WriteAttributeString("ProductCode", symbol.Guid); + } + + if (0 != (symbol.Attributes & WixProductSearchAttributes.Version)) + { + writer.WriteAttributeString("Type", "version"); + } + else if (0 != (symbol.Attributes & WixProductSearchAttributes.Language)) + { + writer.WriteAttributeString("Type", "language"); + } + else if (0 != (symbol.Attributes & WixProductSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (symbol.Attributes & WixProductSearchAttributes.Assignment)) + { + writer.WriteAttributeString("Type", "assignment"); + } + + writer.WriteEndElement(); + } + + private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchSymbol symbol) + { + writer.WriteStartElement("RegistrySearch"); + + base.WriteXml(writer); + + switch (symbol.Root) + { + case RegistryRootType.ClassesRoot: + writer.WriteAttributeString("Root", "HKCR"); + break; + case RegistryRootType.CurrentUser: + writer.WriteAttributeString("Root", "HKCU"); + break; + case RegistryRootType.LocalMachine: + writer.WriteAttributeString("Root", "HKLM"); + break; + case RegistryRootType.Users: + writer.WriteAttributeString("Root", "HKU"); + break; + } + + writer.WriteAttributeString("Key", symbol.Key); + + if (!String.IsNullOrEmpty(symbol.Value)) + { + writer.WriteAttributeString("Value", symbol.Value); + } + + var existenceOnly = 0 != (symbol.Attributes & WixRegistrySearchAttributes.WantExists); + + writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); + + if (0 != (symbol.Attributes & WixRegistrySearchAttributes.Win64)) + { + writer.WriteAttributeString("Win64", "yes"); + } + + if (!existenceOnly) + { + if (0 != (symbol.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/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs new file mode 100644 index 00000000..f9ff23cb --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs @@ -0,0 +1,133 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class ProcessBundleSoftwareTagsCommand + { + public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags) + { + this.Section = section; + this.SoftwareTags = softwareTags; + } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + var bundleInfo = this.Section.Symbols.OfType().FirstOrDefault(); + var bundleId = NormalizeGuid(bundleInfo.BundleId); + var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); + + var uniqueId = String.Concat("wix:bundle/", bundleId); + var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); + + // Try to collect all the software id tags from all the child packages. + var containedTags = CollectPackageTags(this.Section); + + foreach (var bundleTag in this.SoftwareTags) + { + using (var ms = new MemoryStream()) + { + CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); + bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static IEnumerable CollectPackageTags(IntermediateSection section) + { + var tags = new List(); + + var msiPackages = section.Symbols.OfType().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); + if (msiPackages.Any()) + { + var payloadSymbolsById = section.Symbols.OfType().ToDictionary(s => s.Id.Id); + + foreach (var msiPackage in msiPackages) + { + var payload = payloadSymbolsById[msiPackage.PayloadRef]; + + using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) + { + using (var view = db.OpenExecuteView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) + { + foreach (var record in view.Records) + { + tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); + } + } + } + } + } + + return tags; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable containedTags) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + foreach (var containedTag in containedTags) + { + writer.WriteStartElement("Link"); + writer.WriteAttributeString("rel", "component"); + writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + + private class SoftwareTag + { + public string Regid { get; set; } + + public string Id { get; set; } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs new file mode 100644 index 00000000..99effbc7 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.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.Core.Burn.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Symbols; + + internal class ProcessDependencyProvidersCommand + { + public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary facades) + { + this.Messaging = messaging; + this.Section = section; + + this.Facades = facades; + } + + public string BundleProviderKey { get; private set; } + + public Dictionary DependencySymbolsByKey { get; private set; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IDictionary Facades { get; } + + /// + /// Sets the explicitly provided bundle provider key, if provided. And... + /// 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. + /// + public void Execute() + { + var dependencySymbols = this.Section.Symbols.OfType(); + + foreach (var dependency in dependencySymbols) + { + // Sets the provider key for the bundle, if it is not set already. + if (String.IsNullOrEmpty(this.BundleProviderKey)) + { + if (dependency.Bundle) + { + this.BundleProviderKey = dependency.ProviderKey; + } + } + + // Import any authored dependencies. These may merge with imported provides from MSI packages. + var packageId = dependency.ParentRef; + + if (this.Facades.TryGetValue(packageId, out var facade)) + { + if (String.IsNullOrEmpty(dependency.ProviderKey)) + { + switch (facade.SpecificPackageSymbol) + { + // The WixDependencyExtension allows an empty Key for MSIs and MSPs. + case WixBundleMsiPackageSymbol msiPackage: + dependency.ProviderKey = msiPackage.ProductCode; + break; + case WixBundleMspPackageSymbol mspPackage: + dependency.ProviderKey = mspPackage.PatchCode; + break; + } + } + + if (String.IsNullOrEmpty(dependency.Version)) + { + dependency.Version = facade.PackageSymbol.Version; + } + + // If the version is still missing, a version could not be gathered from the package and was not authored. + if (String.IsNullOrEmpty(dependency.Version)) + { + this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId)); + } + + if (String.IsNullOrEmpty(dependency.DisplayName)) + { + dependency.DisplayName = facade.PackageSymbol.DisplayName; + } + } + } + + this.DependencySymbolsByKey = this.GetDependencySymbolsByKey(dependencySymbols); + + // Generate providers for MSI and MSP packages that still do not have providers. + foreach (var facade in this.Facades.Values) + { + string key = null; + + if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + key = msiPackage.ProductCode; + } + else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) + { + key = mspPackage.PatchCode; + } + + if (!String.IsNullOrEmpty(key) && !this.DependencySymbolsByKey.ContainsKey(key)) + { + var dependency = this.Section.AddSymbol(new WixDependencyProviderSymbol(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.Id) + { + ParentRef = facade.PackageId, + ProviderKey = key, + Version = facade.PackageSymbol.Version, + DisplayName = facade.PackageSymbol.DisplayName + }); + + this.DependencySymbolsByKey.Add(dependency.ProviderKey, dependency); + } + } + } + + private Dictionary GetDependencySymbolsByKey(IEnumerable dependencySymbols) + { + var dependencySymbolsByKey = new Dictionary(); + + foreach (var dependency in dependencySymbols) + { + if (dependencySymbolsByKey.TryGetValue(dependency.ProviderKey, out var collision)) + { + // If not a perfect dependency collision, display an error. + if (dependency.ProviderKey != collision.ProviderKey || + dependency.Version != collision.Version || + dependency.DisplayName != collision.DisplayName) + { + this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.ProviderKey, dependency.ParentRef)); + } + } + else + { + dependencySymbolsByKey.Add(dependency.ProviderKey, dependency); + } + } + + return dependencySymbolsByKey; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs new file mode 100644 index 00000000..c678b114 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs @@ -0,0 +1,128 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ResolveDownloadUrlsCommand + { + public ResolveDownloadUrlsCommand(IMessaging messaging, IEnumerable backendExtensions, IEnumerable containers, Dictionary payloadsById) + { + this.Messaging = messaging; + this.BackendExtensions = backendExtensions; + this.Containers = containers; + this.PayloadsById = payloadsById; + } + + private IMessaging Messaging { get; } + + private IEnumerable BackendExtensions { get; } + + private IEnumerable Containers { get; } + + private Dictionary PayloadsById { get; } + + public void Execute() + { + this.ResolveContainerUrls(); + + this.ResolvePayloadUrls(); + } + + private void ResolveContainerUrls() + { + foreach (var container in this.Containers) + { + if (container.Type == ContainerType.Detached) + { + var resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + container.DownloadUrl = resolvedUrl; + } + } + else if (container.Type == ContainerType.Attached) + { + if (!String.IsNullOrEmpty(container.DownloadUrl)) + { + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id)); + } + } + } + } + + private void ResolvePayloadUrls() + { + foreach (var payload in this.PayloadsById.Values) + { + if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName) + { + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id)); + } + } + else + { + var packageId = payload.ParentPackagePayloadRef; + var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl; + var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + payload.DownloadUrl = resolvedUrl; + } + } + } + } + + private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) + { + string resolvedUrl = null; + + foreach (var extension in this.BackendExtensions) + { + resolvedUrl = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + break; + } + } + + if (String.IsNullOrEmpty(resolvedUrl)) + { + // If a URL was not specified but there is a fallback URL that has a format specifier in it + // then use the fallback URL formatter for this URL. + if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl)) + { + var formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName); + if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase)) + { + url = fallbackUrl; + } + } + + if (!String.IsNullOrEmpty(url)) + { + var formattedUrl = String.Format(url, packageId, payloadId, fileName); + + if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var canonicalUri)) + { + resolvedUrl = canonicalUri.AbsoluteUri; + } + else + { + resolvedUrl = null; + } + } + } + + return resolvedUrl; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs new file mode 100644 index 00000000..e88f26ef --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs @@ -0,0 +1,48 @@ +// 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.Xml; + using WixToolset.Data.Symbols; + + internal class SetVariableSearchFacade : BaseSearchFacade + { + public SetVariableSearchFacade(WixSearchSymbol searchSymbol, WixSetVariableSymbol setVariableSymbol) + { + this.SearchSymbol = searchSymbol; + this.SetVariableSymbol = setVariableSymbol; + } + + private WixSetVariableSymbol SetVariableSymbol { get; } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("SetVariable"); + + base.WriteXml(writer); + + if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown) + { + writer.WriteAttributeString("Value", this.SetVariableSymbol.Value); + + switch (this.SetVariableSymbol.Type) + { + case WixBundleVariableType.Formatted: + writer.WriteAttributeString("Type", "formatted"); + break; + case WixBundleVariableType.Numeric: + writer.WriteAttributeString("Type", "numeric"); + break; + case WixBundleVariableType.String: + writer.WriteAttributeString("Type", "string"); + break; + case WixBundleVariableType.Version: + writer.WriteAttributeString("Type", "version"); + break; + } + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/BundleBackend.cs b/src/wix/WixToolset.Core.Burn/BundleBackend.cs new file mode 100644 index 00000000..60e9ea60 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BundleBackend.cs @@ -0,0 +1,77 @@ +// 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.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BundleBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + var command = new BindBundleCommand(context, backendExtensions); + command.Execute(); + + var result = context.ServiceProvider.GetService(); + result.FileTransfers = command.FileTransfers; + result.TrackedFiles = command.TrackedFiles; + result.Wixout = command.Wixout; + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; + } + + public IDecompileResult Decompile(IDecompileContext context) + { + throw new NotImplementedException(); + } + + 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 Intermediate Unbind(IUnbindContext context) + { + var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); + var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); + var messaging = context.ServiceProvider.GetService(); + + using (var reader = BurnReader.Open(messaging, context.InputFilePath)) + { + reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); + reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs new file mode 100644 index 00000000..75c60e56 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs @@ -0,0 +1,117 @@ +// 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.Symbols; + + internal class AutomaticallySlipstreamPatchesCommand + { + public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection packageFacades) + { + this.Section = section; + this.PackageFacades = packageFacades; + } + + private IntermediateSection Section { get; } + + private IEnumerable PackageFacades { get; } + + public void Execute() + { + var msiPackages = new List(); + var targetsProductCode = new Dictionary>(); + var targetsUpgradeCode = new Dictionary>(); + + foreach (var facade in this.PackageFacades) + { + // Keep track of all MSI packages. + if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + msiPackages.Add(msiPackage); + } + else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage && mspPackage.Slipstream) + { + var patchTargetCodeSymbols = this.Section.Symbols + .OfType() + .Where(r => r.PackageRef == facade.PackageId); + + // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. + foreach (var symbol in patchTargetCodeSymbols) + { + if (symbol.TargetsProductCode) + { + if (!targetsProductCode.TryGetValue(symbol.TargetCode, out var symbols)) + { + symbols = new List(); + targetsProductCode.Add(symbol.TargetCode, symbols); + } + + symbols.Add(symbol); + } + else if (symbol.TargetsUpgradeCode) + { + if (!targetsUpgradeCode.TryGetValue(symbol.TargetCode, out var symbols)) + { + symbols = new List(); + targetsUpgradeCode.Add(symbol.TargetCode, symbols); + } + } + } + } + } + + var slipstreamMspIds = new HashSet(); + + // Loop through the MSI and slipstream patches targeting it. + foreach (var msi in msiPackages) + { + if (targetsProductCode.TryGetValue(msi.ProductCode, out var symbols)) + { + foreach (var symbol in symbols) + { + Debug.Assert(symbol.TargetsProductCode); + Debug.Assert(!symbol.TargetsUpgradeCode); + + this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); + } + } + + if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out symbols)) + { + foreach (var symbol in symbols) + { + Debug.Assert(!symbol.TargetsProductCode); + Debug.Assert(symbol.TargetsUpgradeCode); + + this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); + } + + symbols = null; + } + } + } + + private bool TryAddSlipstreamSymbol(HashSet slipstreamMspIds, WixBundleMsiPackageSymbol msiPackage, WixBundlePatchTargetCodeSymbol patchTargetCode) + { + var id = new Identifier(AccessModifier.Section, msiPackage.Id.Id, patchTargetCode.PackageRef); + + if (slipstreamMspIds.Add(id.Id)) + { + this.Section.AddSymbol(new WixBundleSlipstreamMspSymbol(patchTargetCode.SourceLineNumbers) + { + TargetPackageRef = msiPackage.Id.Id, + MspPackageRef = patchTargetCode.PackageRef, + }); + + return true; + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs b/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs new file mode 100644 index 00000000..3b4a4156 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.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.IO; + using System.Security.Cryptography; + using System.Text; + + internal static class BundleHashAlgorithm + { + public static string Hash(FileInfo fileInfo) + { + byte[] hashBytes; + + using (var managed = new SHA512CryptoServiceProvider()) + using (var stream = fileInfo.OpenRead()) + { + hashBytes = managed.ComputeHash(stream); + } + + var sb = new StringBuilder(hashBytes.Length * 2); + for (var i = 0; i < hashBytes.Length; i++) + { + sb.AppendFormat("{0:X2}", hashBytes[i]); + } + + return sb.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs new file mode 100644 index 00000000..1eb3563a --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.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.Burn.Bundles +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// 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 BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; + + public const string BADataFileName = "BootstrapperApplicationData.xml"; + public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; + + public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; + public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; + + // 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. + public BurnCommon(IMessaging messaging, string fileExe) + { + this.Messaging = messaging; + 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; } + + protected IMessaging Messaging { get; } + + public void Dispose() + { + this.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) + { + var bytes = new byte[4096]; + var total = 0; + do + { + var 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 (!this.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) + { + this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); + return false; + } + + this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); + if (BURN_SECTION_VERSION != this.Version) + { + this.Messaging.Write(ErrorMessages.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) + { + this.Messaging.Write(ErrorMessages.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 (!this.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) + { + this.Messaging.Write(ErrorMessages.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)) + { + this.Messaging.Write(ErrorMessages.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 (!this.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)) + { + this.Messaging.Write(ErrorMessages.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)) + { + this.Messaging.Write(ErrorMessages.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 + internal 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 + internal static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 4 <= bytes.Length); + return BurnCommon.ReadUInt16(bytes, offset) + ((UInt32)BurnCommon.ReadUInt16(bytes, offset + 2) << 16); + } + + /// + /// 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 + internal 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/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs new file mode 100644 index 00000000..5b06b31e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs @@ -0,0 +1,212 @@ +// 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.Native; + using WixToolset.Extensibility.Services; + + /// + /// 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 readonly List attachedContainerPayloadNames; + + /// + /// Creates a BurnReader for reading a PE file. + /// + /// + /// File to read. + private BurnReader(IMessaging messaging, string fileExe) + : base(messaging, fileExe) + { + this.attachedContainerPayloadNames = new List(); + } + + /// + /// Gets the underlying stream. + /// + public Stream Stream => this.binaryReader?.BaseStream; + + internal static BurnReader Open(object inputFilePath) + { + throw new NotImplementedException(); + } + + /// + /// Opens a Burn reader. + /// + /// + /// Path to file. + /// Burn reader. + public static BurnReader Open(IMessaging messaging, string fileExe) + { + var reader = new BurnReader(messaging, 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. + /// Scratch directory. + /// 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); + } + + var cabinet = new Cabinet(tempCabPath); + cabinet.Extract(outputDirectory); + + Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); + FileSystem.MoveFile(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)); + FileSystem.MoveFile(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. + /// Scratch directory. + /// 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); + } + + var cabinet = new Cabinet(tempCabPath); + cabinet.Extract(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)); + FileSystem.MoveFile(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/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs new file mode 100644 index 00000000..2d16d11c --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.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.Core.Burn.Bundles +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// 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. + private BurnWriter(IMessaging messaging, string fileExe) + : base(messaging, fileExe) + { + } + + /// + /// Opens a Burn writer. + /// + /// + /// Path to file. + /// Burn writer. + public static BurnWriter Open(IMessaging messaging, string fileExe) + { + BurnWriter writer = new BurnWriter(messaging, 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, string bundleId) + { + if (this.invalidBundle) + { + return false; + } + + var bundleGuid = Guid.Parse(bundleId); + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); + + this.Messaging.Write(VerboseMessages.BundleGuid(bundleId)); + this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); + this.binaryWriter.Write(bundleGuid.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 this.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. + /// Size of the container. + /// Offset of size field for this container in ".wixburn" section data. + /// Number of Burn sections. + /// 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/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs new file mode 100644 index 00000000..a0ee606d --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -0,0 +1,290 @@ +// 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.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CreateBootstrapperApplicationManifestCommand + { + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadSymbols, Dictionary> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) + { + this.Section = section; + this.BundleSymbol = bundleSymbol; + this.ChainPackages = chainPackages; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.Payloads = payloadSymbols; + this.PackagesPayloads = packagesPayloads; + this.IntermediateFolder = intermediateFolder; + this.InternalBurnBackendHelper = internalBurnBackendHelper; + } + + private IntermediateSection Section { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private IEnumerable ChainPackages { get; } + + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + + private int LastUXPayloadIndex { get; } + + private Dictionary Payloads { get; } + + private Dictionary> PackagesPayloads { get; } + + private string IntermediateFolder { get; } + + public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; } + + public string OutputPath { get; private set; } + + public void Execute() + { + this.OutputPath = this.CreateBootstrapperApplicationManifest(); + + this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath); + } + + private string CreateBootstrapperApplicationManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); + + this.WriteBundleInfo(writer); + + this.WritePackageInfo(writer); + + this.WriteFeatureInfo(writer); + + this.WritePayloadInfo(writer); + + this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + return path; + } + + private void WriteBundleInfo(XmlTextWriter writer) + { + writer.WriteStartElement("WixBundleProperties"); + + writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); + writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable); + writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no"); + writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant()); + writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode); + writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); + + writer.WriteEndElement(); + } + + private void WritePackageInfo(XmlTextWriter writer) + { + foreach (var package in this.ChainPackages) + { + if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) + { + continue; + } + + var packagePayload = payloads[package.PackageSymbol.PayloadRef]; + + var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); + + writer.WriteStartElement("WixPackageProperties"); + + writer.WriteAttributeString("Package", package.PackageId); + writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == true ? "yes" : "no"); + + if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName)) + { + writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.Description)) + { + writer.WriteAttributeString("Description", package.PackageSymbol.Description); + } + + writer.WriteAttributeString("DownloadSize", size); + writer.WriteAttributeString("PackageSize", size); + writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); + writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString()); + writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); + writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); + writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); + writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); + + if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + if (!String.IsNullOrEmpty(msiPackage.ProductCode)) + { + writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); + } + + if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); + } + } + else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) + { + if (!String.IsNullOrEmpty(mspPackage.PatchCode)) + { + writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); + } + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.Version)) + { + writer.WriteAttributeString("Version", package.PackageSymbol.Version); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) + { + writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); + } + + switch (package.PackageSymbol.Cache) + { + case YesNoAlwaysType.No: + writer.WriteAttributeString("Cache", "remove"); + break; + case YesNoAlwaysType.Yes: + writer.WriteAttributeString("Cache", "keep"); + break; + case YesNoAlwaysType.Always: + writer.WriteAttributeString("Cache", "force"); + break; + } + + writer.WriteEndElement(); + } + } + + private void WriteFeatureInfo(XmlTextWriter writer) + { + var featureSymbols = this.Section.Symbols.OfType(); + + foreach (var featureSymbol in featureSymbols) + { + writer.WriteStartElement("WixPackageFeatureInfo"); + + writer.WriteAttributeString("Package", featureSymbol.PackageRef); + writer.WriteAttributeString("Feature", featureSymbol.Name); + writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture)); + + if (!String.IsNullOrEmpty(featureSymbol.Parent)) + { + writer.WriteAttributeString("Parent", featureSymbol.Parent); + } + + if (!String.IsNullOrEmpty(featureSymbol.Title)) + { + writer.WriteAttributeString("Title", featureSymbol.Title); + } + + if (!String.IsNullOrEmpty(featureSymbol.Description)) + { + writer.WriteAttributeString("Description", featureSymbol.Description); + } + + writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Directory", featureSymbol.Directory); + writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture)); + + writer.WriteEndElement(); + } + } + + private void WritePayloadInfo(XmlTextWriter writer) + { + foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) + { + var packageId = kvp.Key; + var payloadsById = kvp.Value; + + foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) + { + this.WritePayloadInfo(writer, payloadSymbol, packageId); + } + } + + foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal)) + { + this.WritePayloadInfo(writer, payloadSymbol, null); + } + } + + private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId) + { + writer.WriteStartElement("WixPayloadProperties"); + + if (!String.IsNullOrEmpty(packageId)) + { + writer.WriteAttributeString("Package", packageId); + } + + writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); + + if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) + { + writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); + } + + writer.WriteAttributeString("Name", payloadSymbol.Name); + writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + + if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); + } + + writer.WriteEndElement(); + } + + private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) + { + var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName); + + var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = BurnCommon.BADataFileName, + SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, + Compressed = true, + UnresolvedSourceFile = baManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }); + + var fileInfo = new FileInfo(baManifestPath); + + symbol.FileSize = (int)fileInfo.Length; + + symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); + + return symbol; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs new file mode 100644 index 00000000..b802f556 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -0,0 +1,325 @@ +// 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.Reflection; + using System.Text; + using System.Xml; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.Resources; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CreateBundleExeCommand + { + public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.IntermediateFolder = intermediateFolder; + this.OutputPath = outputPath; + this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; + this.BundleSymbol = bundleSymbol; + this.UXContainer = uxContainer; + this.Containers = containers; + } + + public IFileTransfer Transfer { get; private set; } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private string IntermediateFolder { get; } + + private string OutputPath { get; } + + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private WixBundleContainerSymbol UXContainer { get; } + + private IEnumerable Containers { get; } + + public void Execute() + { + var bundleFilename = Path.GetFileName(this.OutputPath); + + // Copy the burn.exe to a writable location then mark it to be moved to its final build location. + + var stubPlatform = this.BundleSymbol.Platform.ToString(); + var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); + + if (stubPlatform != "X86") + { + this.Messaging.Write(WarningMessages.ExperimentalBundlePlatform(stubPlatform)); + } + + var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename); + + this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile)); + + if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename)); + } + + this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers); + + FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); + File.SetAttributes(bundleTempPath, FileAttributes.Normal); + + var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol); + + var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion); + + UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData); + + // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers + // if they should be attached. + using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath)) + { + var burnStubFile = new FileInfo(bundleTempPath); + writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId); + + // Always attach the UX container first + writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX); + + // Now append all other attached containers + foreach (var container in this.Containers) + { + if (ContainerType.Attached == container.Type) + { + // The container was only created if it had payloads. + if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) + { + writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); + } + } + } + } + } + + private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) + { + const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1"; + const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3"; + const string compatv1Namespace = "urn:schemas-microsoft-com:compatibility.v1"; + const string ws2005Namespace = "http://schemas.microsoft.com/SMI/2005/WindowsSettings"; + const string ws2016Namespace = "http://schemas.microsoft.com/SMI/2016/WindowsSettings"; + const string ws2017Namespace = "http://schemas.microsoft.com/SMI/2017/WindowsSettings"; + + var bundleFileName = Path.GetFileName(outputPath); + var bundleAssemblyVersion = windowsAssemblyVersion.ToString(); + var bundlePlatform = bundleSymbol.Platform == Platform.X64 ? "amd64" : bundleSymbol.Platform.ToString().ToLower(); + var bundleDescription = bundleSymbol.Name; + + using (var memoryStream = new MemoryStream()) + using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8)) + { + writer.WriteStartDocument(); + + writer.WriteStartElement("assembly", asmv1Namespace); + writer.WriteAttributeString("manifestVersion", "1.0"); + + writer.WriteStartElement("assemblyIdentity"); + writer.WriteAttributeString("name", bundleFileName); + writer.WriteAttributeString("version", bundleAssemblyVersion); + writer.WriteAttributeString("processorArchitecture", bundlePlatform); + writer.WriteAttributeString("type", "win32"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(bundleDescription)) + { + writer.WriteStartElement("description"); + writer.WriteString(bundleDescription); + writer.WriteEndElement(); + } + + writer.WriteStartElement("dependency"); + writer.WriteStartElement("dependentAssembly"); + writer.WriteStartElement("assemblyIdentity"); + writer.WriteAttributeString("name", "Microsoft.Windows.Common-Controls"); + writer.WriteAttributeString("version", "6.0.0.0"); + writer.WriteAttributeString("processorArchitecture", bundlePlatform); + writer.WriteAttributeString("publicKeyToken", "6595b64144ccf1df"); + writer.WriteAttributeString("language", "*"); + writer.WriteAttributeString("type", "win32"); + writer.WriteEndElement(); // + writer.WriteEndElement(); // + writer.WriteEndElement(); // + + writer.WriteStartElement("compatibility", compatv1Namespace); + writer.WriteStartElement("application"); + + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{e2011457-1546-43c5-a5fe-008deee3d3f0}"); // Windows Vista + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"); // Windows 7 + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"); // Windows 8 + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"); // Windows 8.1 + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"); // Windows 10 + writer.WriteEndElement(); + + writer.WriteEndElement(); // + writer.WriteEndElement(); // + + writer.WriteStartElement("trustInfo", asmv3Namespace); + writer.WriteStartElement("security"); + writer.WriteStartElement("requestedPrivileges"); + writer.WriteStartElement("requestedExecutionLevel"); + writer.WriteAttributeString("level", "asInvoker"); + writer.WriteAttributeString("uiAccess", "false"); + writer.WriteEndElement(); // + writer.WriteEndElement(); // + writer.WriteEndElement(); // + writer.WriteEndElement(); // + + if (bootstrapperApplicationSymbol.DpiAwareness != WixBootstrapperApplicationDpiAwarenessType.Unaware) + { + string dpiAwareValue = null; + string dpiAwarenessValue = null; + string gdiScalingValue = null; + + switch(bootstrapperApplicationSymbol.DpiAwareness) + { + case WixBootstrapperApplicationDpiAwarenessType.GdiScaled: + gdiScalingValue = "true"; + break; + case WixBootstrapperApplicationDpiAwarenessType.PerMonitor: + dpiAwareValue = "true/pm"; + break; + case WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2: + dpiAwareValue = "true/pm"; + dpiAwarenessValue = "PerMonitorV2, PerMonitor"; + break; + case WixBootstrapperApplicationDpiAwarenessType.System: + dpiAwareValue = "true"; + break; + } + + writer.WriteStartElement("application", asmv3Namespace); + writer.WriteStartElement("windowsSettings"); + + if (dpiAwareValue != null) + { + writer.WriteStartElement("dpiAware", ws2005Namespace); + writer.WriteString(dpiAwareValue); + writer.WriteEndElement(); + } + + if (dpiAwarenessValue != null) + { + writer.WriteStartElement("dpiAwareness", ws2016Namespace); + writer.WriteString(dpiAwarenessValue); + writer.WriteEndElement(); + } + + if (gdiScalingValue != null) + { + writer.WriteStartElement("gdiScaling", ws2017Namespace); + writer.WriteString(gdiScalingValue); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); // + writer.WriteEndElement(); // + } + + writer.WriteEndDocument(); // + writer.Close(); + + return memoryStream.ToArray(); + } + } + + private static Version GetWindowsAssemblyVersion(WixBundleSymbol bundleSymbol) + { + // Ensure the bundle info provides a full four part version. + var fourPartVersion = new Version(bundleSymbol.Version); + var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; + var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; + var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; + var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; + + if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) + { + throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleSymbol.SourceLineNumbers, "Bundle", bundleSymbol.Version)); + } + + return new Version(major, minor, build, revision); + } + + private static void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version windowsAssemblyVersion, byte[] applicationManifestData) + { + const int burnLocale = 1033; + var resources = new Dtf.Resources.ResourceCollection(); + var version = new Dtf.Resources.VersionResource("#1", burnLocale); + + version.Load(bundleTempPath); + resources.Add(version); + + version.FileVersion = windowsAssemblyVersion; + version.ProductVersion = windowsAssemblyVersion; + + var strings = version[burnLocale] ?? version.Add(burnLocale); + 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.Manufacturer)) + { + strings["CompanyName"] = bundleInfo.Manufacturer; + } + else + { + strings["CompanyName"] = String.Empty; + } + + if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile)) + { + var iconGroup = new Dtf.Resources.GroupIconResource("#1", burnLocale); + iconGroup.ReadFromFile(bundleInfo.IconSourceFile); + resources.Add(iconGroup); + + foreach (var icon in iconGroup.Icons) + { + resources.Add(icon); + } + } + + if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile)) + { + var bitmap = new Dtf.Resources.BitmapResource("#1", burnLocale); + bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile); + resources.Add(bitmap); + } + + var manifestResource = new Resource(ResourceType.Manifest, "#1", burnLocale, applicationManifestData); + resources.Add(manifestResource); + + resources.Save(bundleTempPath); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs new file mode 100644 index 00000000..e587413e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs @@ -0,0 +1,99 @@ +// 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.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CreateBundleExtensionManifestCommand + { + public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) + { + this.Section = section; + this.BundleSymbol = bundleSymbol; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.IntermediateFolder = intermediateFolder; + this.InternalBurnBackendHelper = internalBurnBackendHelper; + } + + private IntermediateSection Section { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + + private int LastUXPayloadIndex { get; } + + private string IntermediateFolder { get; } + + public WixBundlePayloadSymbol BundleExtensionManifestPayloadRow { get; private set; } + + public string OutputPath { get; private set; } + + public void Execute() + { + this.OutputPath = this.CreateBundleExtensionManifest(); + + this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(this.OutputPath); + } + + private string CreateBundleExtensionManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); + + this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + return path; + } + + private WixBundlePayloadSymbol CreateBundleExtensionManifestPayloadRow(string bextManifestPath) + { + var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); + + this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Container, + ParentId = BurnConstants.BurnUXContainerName, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + + var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = BurnCommon.BundleExtensionDataFileName, + SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, + Compressed = true, + UnresolvedSourceFile = bextManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }); + + var fileInfo = new FileInfo(bextManifestPath); + + symbol.FileSize = (int)fileInfo.Length; + + symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); + + return symbol; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs new file mode 100644 index 00000000..5655d23d --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -0,0 +1,700 @@ +// 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.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class CreateBurnManifestCommand + { + public CreateBurnManifestCommand(string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, Dictionary> packagesPayloads, IEnumerable orderedSearches, string intermediateFolder) + { + this.ExecutableName = executableName; + this.Section = section; + this.BundleSymbol = bundleSymbol; + this.Chain = chainSymbol; + this.Containers = containers; + this.OrderedPackages = orderedPackages; + this.RollbackBoundaries = boundaries; + this.UXContainerPayloads = uxPayloads; + this.Payloads = allPayloadsById; + this.PackagesPayloads = packagesPayloads; + this.OrderedSearches = orderedSearches; + this.IntermediateFolder = intermediateFolder; + } + + public string OutputPath { get; private set; } + + private string ExecutableName { get; } + + private IntermediateSection Section { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private WixChainSymbol Chain { get; } + + private IEnumerable RollbackBoundaries { get; } + + private IEnumerable OrderedPackages { get; } + + private IEnumerable OrderedSearches { get; } + + private Dictionary Payloads { get; } + + private Dictionary> PackagesPayloads { get; } + + private IEnumerable Containers { get; } + + private IEnumerable UXContainerPayloads { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); + + using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) + { + writer.WriteStartDocument(); + + writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); + + // Write the condition, if there is one + if (null != this.BundleSymbol.Condition) + { + writer.WriteElementString("Condition", this.BundleSymbol.Condition); + } + + // Write the log element if default logging wasn't disabled. + if (!String.IsNullOrEmpty(this.BundleSymbol.LogPrefix)) + { + writer.WriteStartElement("Log"); + if (!String.IsNullOrEmpty(this.BundleSymbol.LogPathVariable)) + { + writer.WriteAttributeString("PathVariable", this.BundleSymbol.LogPathVariable); + } + writer.WriteAttributeString("Prefix", this.BundleSymbol.LogPrefix); + writer.WriteAttributeString("Extension", this.BundleSymbol.LogExtension); + writer.WriteEndElement(); + } + + + // Get update if specified. + var updateSymbol = this.Section.Symbols.OfType().FirstOrDefault(); + + if (null != updateSymbol) + { + writer.WriteStartElement("Update"); + writer.WriteAttributeString("Location", updateSymbol.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). + var relatedBundles = this.Section.Symbols.OfType(); + var distinctRelatedBundles = new HashSet(); + + foreach (var relatedBundle in relatedBundles) + { + if (distinctRelatedBundles.Add(relatedBundle.BundleId)) + { + writer.WriteStartElement("RelatedBundle"); + writer.WriteAttributeString("Id", relatedBundle.BundleId); + writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); + writer.WriteEndElement(); + } + } + + // Write the variables + var variables = this.Section.Symbols.OfType(); + + foreach (var variable in variables) + { + writer.WriteStartElement("Variable"); + writer.WriteAttributeString("Id", variable.Id.Id); + if (variable.Type != WixBundleVariableType.Unknown) + { + writer.WriteAttributeString("Value", variable.Value); + + switch (variable.Type) + { + case WixBundleVariableType.Formatted: + writer.WriteAttributeString("Type", "formatted"); + break; + case WixBundleVariableType.Numeric: + writer.WriteAttributeString("Type", "numeric"); + break; + case WixBundleVariableType.String: + writer.WriteAttributeString("Type", "string"); + break; + case WixBundleVariableType.Version: + writer.WriteAttributeString("Type", "version"); + break; + } + } + writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); + writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); + writer.WriteEndElement(); + } + + // Write the searches + foreach (var searchinfo in this.OrderedSearches) + { + searchinfo.WriteXml(writer); + } + + // write the UX element + writer.WriteStartElement("UX"); + if (!String.IsNullOrEmpty(this.BundleSymbol.SplashScreenSourceFile)) + { + writer.WriteAttributeString("SplashScreen", "yes"); + } + + // write the UX allPayloads... + foreach (var payload in this.UXContainerPayloads) + { + this.WriteBurnManifestUXPayload(writer, payload); + } + + writer.WriteEndElement(); // + + foreach (var container in this.Containers) + { + if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) + { + writer.WriteStartElement("Container"); + this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); + writer.WriteEndElement(); + } + } + + foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName)) + { + this.WriteBurnManifestPayload(writer, payload); + } + + foreach (var rollbackBoundary in this.RollbackBoundaries) + { + writer.WriteStartElement("RollbackBoundary"); + writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); + writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes"); + writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no"); + writer.WriteEndElement(); + } + + // Write the registration information... + writer.WriteStartElement("Registration"); + + writer.WriteAttributeString("Id", this.BundleSymbol.BundleId); + writer.WriteAttributeString("ExecutableName", this.ExecutableName); + writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Tag", this.BundleSymbol.Tag); + writer.WriteAttributeString("Version", this.BundleSymbol.Version); + writer.WriteAttributeString("ProviderKey", this.BundleSymbol.ProviderKey); + + writer.WriteStartElement("Arp"); + writer.WriteAttributeString("Register", (this.BundleSymbol.DisableModify || this.BundleSymbol.SingleChangeUninstallButton) && this.BundleSymbol.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove. + writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); + writer.WriteAttributeString("DisplayVersion", this.BundleSymbol.Version); + + if (!String.IsNullOrEmpty(this.BundleSymbol.Manufacturer)) + { + writer.WriteAttributeString("Publisher", this.BundleSymbol.Manufacturer); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.HelpUrl)) + { + writer.WriteAttributeString("HelpLink", this.BundleSymbol.HelpUrl); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.HelpTelephone)) + { + writer.WriteAttributeString("HelpTelephone", this.BundleSymbol.HelpTelephone); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.AboutUrl)) + { + writer.WriteAttributeString("AboutUrl", this.BundleSymbol.AboutUrl); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.UpdateUrl)) + { + writer.WriteAttributeString("UpdateUrl", this.BundleSymbol.UpdateUrl); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.ParentName)) + { + writer.WriteAttributeString("ParentDisplayName", this.BundleSymbol.ParentName); + } + + if (this.BundleSymbol.DisableModify) + { + writer.WriteAttributeString("DisableModify", "yes"); + } + + if (this.BundleSymbol.DisableRemove) + { + writer.WriteAttributeString("DisableRemove", "yes"); + } + + if (this.BundleSymbol.SingleChangeUninstallButton) + { + writer.WriteAttributeString("DisableModify", "button"); + } + writer.WriteEndElement(); // + + // Get update registration if specified. + var updateRegistrationInfo = this.Section.Symbols.OfType().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(); // + } + + foreach (var bundleTagSymbol in this.Section.Symbols.OfType()) + { + writer.WriteStartElement("SoftwareTag"); + writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); + writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); + writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); + writer.WriteCData(bundleTagSymbol.Xml); + 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. + var targetCodesByPatch = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var msiFeaturesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); + var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); + var commandLinesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.WixBundlePackageRef); + + var dependenciesByPackage = this.Section.Symbols.OfType().ToLookup(p => p.ParentRef); + + + // Build up the list of target codes from all the MSPs in the chain. + var targetCodes = new List(); + + foreach (var package in this.OrderedPackages) + { + writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageSymbol.Type)); + + writer.WriteAttributeString("Id", package.PackageId); + + switch (package.PackageSymbol.Cache) + { + case YesNoAlwaysType.No: + writer.WriteAttributeString("Cache", "remove"); + break; + case YesNoAlwaysType.Yes: + writer.WriteAttributeString("Cache", "keep"); + break; + case YesNoAlwaysType.Always: + writer.WriteAttributeString("Cache", "force"); + break; + } + + writer.WriteAttributeString("CacheId", package.PackageSymbol.CacheId); + writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageSymbol.InstallSize)); + writer.WriteAttributeString("Size", Convert.ToString(package.PackageSymbol.Size)); + writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageSymbol.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); + writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == false ? "no" : "yes"); + + if (null != package.PackageSymbol.RollbackBoundaryRef) + { + writer.WriteAttributeString("RollbackBoundaryForward", package.PackageSymbol.RollbackBoundaryRef); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackBoundaryBackwardRef)) + { + writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageSymbol.RollbackBoundaryBackwardRef); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.LogPathVariable)) + { + writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackLogPathVariable)) + { + writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) + { + writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); + } + + if (package.SpecificPackageSymbol is WixBundleExePackageSymbol exePackage) // EXE + { + writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition); + writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand); + writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand); + writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand); + writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no"); + if (!String.IsNullOrEmpty(exePackage.ExeProtocol)) + { + writer.WriteAttributeString("Protocol", exePackage.ExeProtocol); + } + } + else if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) // MSI + { + writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); + writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Version", msiPackage.ProductVersion); + if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); + } + } + else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) // MSP + { + writer.WriteAttributeString("PatchCode", mspPackage.PatchCode); + writer.WriteAttributeString("PatchXml", mspPackage.PatchXml); + + // 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 (!mspPackage.TargetUnspecified) + { + var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id]; + + targetCodes.AddRange(patchTargetCodes); + } + else // we have a patch that targets the world, so throw the whole list away. + { + targetCodes = null; + } + } + } + else if (package.SpecificPackageSymbol is WixBundleMsuPackageSymbol msuPackage) // MSU + { + writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition); + writer.WriteAttributeString("KB", msuPackage.MsuKB); + } + + var packageMsiFeatures = msiFeaturesByPackage[package.PackageId]; + + foreach (var feature in packageMsiFeatures) + { + writer.WriteStartElement("MsiFeature"); + writer.WriteAttributeString("Id", feature.Name); + writer.WriteEndElement(); + } + + var packageMsiProperties = msiPropertiesByPackage[package.PackageId]; + + foreach (var 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(); + } + + var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId]; + + foreach (var slipstreamMsp in packageSlipstreamMsps) + { + writer.WriteStartElement("SlipstreamMsp"); + writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef); + writer.WriteEndElement(); + } + + var packageExitCodes = exitCodesByPackage[package.PackageId]; + + foreach (var 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(); + } + + var packageCommandLines = commandLinesByPackage[package.PackageId]; + + foreach (var 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. + var dependencies = dependenciesByPackage[package.PackageId]; + + foreach (var dependency in dependencies) + { + writer.WriteStartElement("Provides"); + writer.WriteAttributeString("Key", dependency.ProviderKey); + + if (!String.IsNullOrEmpty(dependency.Version)) + { + writer.WriteAttributeString("Version", dependency.Version); + } + + if (!String.IsNullOrEmpty(dependency.DisplayName)) + { + writer.WriteAttributeString("DisplayName", dependency.DisplayName); + } + + if (dependency.Imported) + { + // The package dependency was explicitly authored into the manifest. + writer.WriteAttributeString("Imported", "yes"); + } + + writer.WriteEndElement(); + } + + var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; + + foreach (var related in packageRelatedPackages) + { + writer.WriteStartElement("RelatedPackage"); + writer.WriteAttributeString("Id", related.RelatedId); + 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"); + + var 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 + var packagePayloadId = package.PackageSymbol.PayloadRef; + writer.WriteStartElement("PayloadRef"); + writer.WriteAttributeString("Id", packagePayloadId); + writer.WriteEndElement(); + + var packagePayloads = this.PackagesPayloads[package.PackageId]; + + foreach (var payload in packagePayloads.Values) + { + if (payload.Id.Id != packagePayloadId) + { + writer.WriteStartElement("PayloadRef"); + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); // + } + writer.WriteEndElement(); // + + if (null != targetCodes) + { + foreach (var targetCode in targetCodes) + { + writer.WriteStartElement("PatchTargetCode"); + writer.WriteAttributeString("TargetCode", targetCode.TargetCode); + writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); + writer.WriteEndElement(); + } + } + + // Write the ApprovedExeForElevation elements. + var approvedExesForElevation = this.Section.Symbols.OfType(); + + foreach (var approvedExeForElevation in approvedExesForElevation) + { + writer.WriteStartElement("ApprovedExeForElevation"); + writer.WriteAttributeString("Id", approvedExeForElevation.Id.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(); + } + + // Write the BundleExtension elements. + var bundleExtensions = this.Section.Symbols.OfType(); + + foreach (var bundleExtension in bundleExtensions) + { + writer.WriteStartElement("BundleExtension"); + writer.WriteAttributeString("Id", bundleExtension.Id.Id); + writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); + + writer.WriteEndElement(); + } + + writer.WriteEndDocument(); // + } + } + + private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerSymbol container) + { + writer.WriteAttributeString("Id", container.Id.Id); + writer.WriteAttributeString("FileSize", container.Size.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", container.Hash); + + if (ContainerType.Detached == container.Type) + { + if (!String.IsNullOrEmpty(container.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); + } + + writer.WriteAttributeString("FilePath", container.Name); + } + else if (ContainerType.Attached == container.Type) + { + writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. + writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Attached", "yes"); + writer.WriteAttributeString("Primary", "yes"); + } + } + + private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) + { + writer.WriteStartElement("Payload"); + + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteAttributeString("FilePath", payload.Name); + writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", payload.Hash); + + if (payload.LayoutOnly) + { + writer.WriteAttributeString("LayoutOnly", "yes"); + } + + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); + } + + switch (payload.Packaging) + { + case PackagingType.Embedded: // this means it's in a container. + Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef); + + writer.WriteAttributeString("Packaging", "embedded"); + writer.WriteAttributeString("SourcePath", payload.EmbeddedId); + writer.WriteAttributeString("Container", payload.ContainerRef); + break; + + case PackagingType.External: + writer.WriteAttributeString("Packaging", "external"); + writer.WriteAttributeString("SourcePath", payload.Name); + break; + } + + writer.WriteEndElement(); + } + + private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) + { + Debug.Assert(PackagingType.Embedded == payload.Packaging); + Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef); + + writer.WriteStartElement("Payload"); + + // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used. + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteAttributeString("FilePath", payload.Name); + writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", payload.Hash); + writer.WriteAttributeString("Packaging", "embedded"); + writer.WriteAttributeString("SourcePath", payload.EmbeddedId); + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs new file mode 100644 index 00000000..87a63cc3 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs @@ -0,0 +1,70 @@ +// 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 WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Creates cabinet files. + /// + internal class CreateContainerCommand + { + public CreateContainerCommand(IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) + { + this.Payloads = payloads; + this.OutputPath = outputPath; + this.CompressionLevel = compressionLevel; + } + + public CreateContainerCommand(string manifestPath, IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) + { + this.ManifestFile = manifestPath; + this.Payloads = payloads; + this.OutputPath = outputPath; + this.CompressionLevel = compressionLevel; + } + + private CompressionLevel? CompressionLevel { get; } + + private string ManifestFile { get; } + + private string OutputPath { get; } + + private IEnumerable Payloads { get; } + + public string Hash { get; private set; } + + public long Size { get; private set; } + + public void Execute() + { + var cabinetPath = Path.GetFullPath(this.OutputPath); + + var files = new List(); + + // If a manifest was provided always add it as "payload 0" to the container. + if (!String.IsNullOrEmpty(this.ManifestFile)) + { + files.Add(new CabinetCompressFile(this.ManifestFile, "0")); + } + + files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId))); + + var cab = new Cabinet(cabinetPath); + cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Medium); + + // Now that the container is created, set the outputs of the command. + var fileInfo = new FileInfo(cabinetPath); + + this.Hash = BundleHashAlgorithm.Hash(fileInfo); + + this.Size = fileInfo.Length; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs new file mode 100644 index 00000000..f020ed84 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -0,0 +1,151 @@ +// 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.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CreateNonUXContainers + { + public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + { + this.BackendHelper = backendHelper; + this.Messaging = messaging; + this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; + this.Containers = containerSymbols; + this.PayloadSymbols = payloadSymbols; + this.IntermediateFolder = intermediateFolder; + this.LayoutFolder = layoutFolder; + this.DefaultCompressionLevel = defaultCompressionLevel; + } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable TrackedFiles { get; private set; } + + public WixBundleContainerSymbol UXContainer { get; set; } + + public IEnumerable UXContainerPayloads { get; private set; } + + private IEnumerable Containers { get; } + + private IBackendHelper BackendHelper { get; } + + private IMessaging Messaging { get; } + + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } + + private Dictionary PayloadSymbols { get; } + + private string IntermediateFolder { get; } + + private string LayoutFolder { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public void Execute() + { + var fileTransfers = new List(); + var trackedFiles = new List(); + var uxPayloadSymbols = new List(); + + var attachedContainerIndex = 1; // count starts at one because UX container is "0". + + var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef); + + foreach (var container in this.Containers) + { + var containerId = container.Id.Id; + + var containerPayloads = payloadsByContainer[containerId]; + + if (!containerPayloads.Any()) + { + if (containerId != BurnConstants.BurnDefaultAttachedContainerName) + { + this.Messaging.Write(BurnBackendWarnings.EmptyContainer(container.SourceLineNumbers, containerId)); + } + } + else if (BurnConstants.BurnUXContainerName == containerId) + { + this.UXContainer = container; + + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + container.AttachedContainerIndex = 0; + + // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first + // in the list since that is the Payload that Burn attempts to load. + var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id; + + foreach (var uxPayload in containerPayloads) + { + if (uxPayload.Id.Id == baPayloadId) + { + uxPayloadSymbols.Insert(0, uxPayload); + } + else + { + uxPayloadSymbols.Add(uxPayload); + } + } + } + else + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + + // Add detached containers to the list of file transfers. + if (ContainerType.Detached == container.Type) + { + var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers); + fileTransfers.Add(transfer); + } + else // update the attached container index. + { + Debug.Assert(ContainerType.Attached == container.Type); + + container.AttachedContainerIndex = attachedContainerIndex; + ++attachedContainerIndex; + } + } + } + + foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) + { + if (container.Type == ContainerType.Attached && attachedContainerIndex > 2 && container.Id.Id != BurnConstants.BurnDefaultAttachedContainerName) + { + this.Messaging.Write(BurnBackendErrors.MultipleAttachedContainersUnsupported(container.SourceLineNumbers, container.Id.Id)); + } + } + + if (!this.Messaging.EncounteredError) + { + foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) + { + this.CreateContainer(container, payloadsByContainer[container.Id.Id]); + trackedFiles.Add(this.BackendHelper.TrackFile(container.WorkingPath, TrackedFileType.Temporary, container.SourceLineNumbers)); + } + } + + this.UXContainerPayloads = uxPayloadSymbols; + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + } + + private void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads) + { + var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs new file mode 100644 index 00000000..bfb6b918 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs @@ -0,0 +1,137 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class DetectPayloadCollisionsCommand + { + public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary containerSymbols, IEnumerable packages, Dictionary payloadSymbols, Dictionary> packagePayloads) + { + this.Messaging = messaging; + this.Containers = containerSymbols; + this.Packages = packages; + this.PayloadSymbols = payloadSymbols; + this.PackagePayloads = packagePayloads; + } + + private IMessaging Messaging { get; } + + private Dictionary Containers { get; } + + private IEnumerable Packages { get; } + + private Dictionary PayloadSymbols { get; } + + private Dictionary> PackagePayloads { get; } + + public void Execute() + { + this.DetectAttachedContainerCollisions(); + this.DetectExternalCollisions(); + this.DetectPackageCacheCollisions(); + } + + public void DetectAttachedContainerCollisions() + { + var attachedContainerPayloadsByNameByContainer = new Dictionary>(); + + foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded)) + { + var containerId = payload.ContainerRef; + var container = this.Containers[containerId]; + if (container.Type == ContainerType.Attached) + { + if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName)) + { + attachedContainerPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName); + } + + if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) + { + attachedContainerPayloadsByName.Add(payload.Name, payload); + } + else + { + if (containerId == BurnConstants.BurnUXContainerName) + { + this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); + } + else + { + this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); + } + } + } + } + } + + public void DetectExternalCollisions() + { + var externalPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External)) + { + if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol)) + { + externalPayloadsByName.Add(payload.Name, payload); + } + else + { + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); + } + } + + foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached)) + { + if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol)) + { + externalPayloadsByName.Add(container.Name, container); + } + else + { + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name)); + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); + } + } + } + + public void DetectPackageCacheCollisions() + { + var packageCachePayloadsByNameByCacheId = new Dictionary>(); + + foreach (var packageFacade in this.Packages) + { + var packagePayloads = this.PackagePayloads[packageFacade.PackageId]; + if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName)) + { + packageCachePayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName); + } + + foreach (var payload in packagePayloads.Values) + { + if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) + { + packageCachePayloadsByName.Add(payload.Name, payload); + } + else + { + this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId)); + this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers)); + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs new file mode 100644 index 00000000..b8b256fd --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs @@ -0,0 +1,181 @@ +// 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 System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class GetPackageFacadesCommand + { + public GetPackageFacadesCommand(IMessaging messaging, IEnumerable chainPackageSymbols, IntermediateSection section) + { + this.Messaging = messaging; + this.ChainPackageSymbols = chainPackageSymbols; + this.Section = section; + } + + private IEnumerable ChainPackageSymbols { get; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public IDictionary PackageFacades { get; private set; } + + public void Execute() + { + var wixGroupPackagesGroupedById = this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId); + var exePackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msiPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var mspPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msuPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var exePackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msiPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var mspPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msuPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + var facades = new Dictionary(); + + foreach (var package in this.ChainPackageSymbols) + { + var id = package.Id.Id; + + IntermediateSymbol packagePayload = null; + foreach (var wixGroup in wixGroupPackagesGroupedById[id]) + { + if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) + { + IntermediateSymbol tempPackagePayload = null; + if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload)) + { + if (package.Type == WixBundlePackageType.Exe) + { + tempPackagePayload = exePackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msi) + { + tempPackagePayload = msiPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msp) + { + tempPackagePayload = mspPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msu) + { + tempPackagePayload = msuPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId)); + } + + if (tempPackagePayload != null) + { + if (packagePayload == null) + { + packagePayload = tempPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id)); + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers)); + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers)); + } + } + } + } + + if (packagePayload == null) + { + this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString())); + } + else + { + package.PayloadRef = packagePayload.Id.Id; + } + + switch (package.Type) + { + case WixBundlePackageType.Exe: + if (exePackages.TryGetValue(id, out var exePackage)) + { + facades.Add(id, new PackageFacade(package, exePackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id)); + } + break; + + case WixBundlePackageType.Msi: + if (msiPackages.TryGetValue(id, out var msiPackage)) + { + facades.Add(id, new PackageFacade(package, msiPackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id)); + } + break; + + case WixBundlePackageType.Msp: + if (mspPackages.TryGetValue(id, out var mspPackage)) + { + facades.Add(id, new PackageFacade(package, mspPackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id)); + } + break; + + case WixBundlePackageType.Msu: + if (msuPackages.TryGetValue(id, out var msuPackage)) + { + facades.Add(id, new PackageFacade(package, msuPackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id)); + } + break; + } + } + + this.PackageFacades = facades; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new file mode 100644 index 00000000..ccf6b1c2 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs @@ -0,0 +1,171 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class OrderPackagesAndRollbackBoundariesCommand + { + public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, IDictionary packageFacades) + { + this.Messaging = messaging; + this.Section = section; + this.PackageFacades = packageFacades; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IDictionary PackageFacades { get; } + + public IEnumerable OrderedPackageFacades { get; private set; } + + public IEnumerable UsedRollbackBoundaries { get; private set; } + + public void Execute() + { + var groupSymbols = this.Section.Symbols.OfType().ToList(); + var boundariesById = this.Section.Symbols.OfType().ToDictionary(b => b.Id.Id); + + var orderedFacades = new List(); + var 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. + var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true }; + var lastRollbackBoundary = pendingRollbackBoundary; + var boundaryHadX86Package = false; + var warnedMsiTransaction = false; + + foreach (var groupSymbol in groupSymbols) + { + if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId) + { + if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade)) + { + var insideMsiTransaction = lastRollbackBoundary?.Transaction ?? false; + + if (null != pendingRollbackBoundary) + { + // If we used the default boundary, ensure the symbol is added to the section. + if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId) + { + this.Section.AddSymbol(pendingRollbackBoundary); + } + + if (insideMsiTransaction && !warnedMsiTransaction) + { + warnedMsiTransaction = true; + this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers)); + } + + usedBoundaries.Add(pendingRollbackBoundary); + facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id; + pendingRollbackBoundary = null; + + boundaryHadX86Package = !facade.PackageSymbol.Win64; + } + + // Error if MSI transaction has x86 package preceding x64 packages + if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64) + { + this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers)); + } + + boundaryHadX86Package |= !facade.PackageSymbol.Win64; + + orderedFacades.Add(facade); + } + else // must be a rollback boundary. + { + // Discard the next rollback boundary if we have a previously defined boundary. + var nextRollbackBoundary = boundariesById[groupSymbol.ChildId]; + if (null != pendingRollbackBoundary) + { + if (pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId) + { + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); + } + } + + lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary; + } + } + } + + if (null != pendingRollbackBoundary) + { + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id)); + } + + // 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 (var package in orderedFacades) + { + if (null != package.PackageSymbol.RollbackBoundaryRef) + { + if (null != previousFacade) + { + previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; + } + + previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef; + } + + previousFacade = package; + } + + if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) + { + previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; + } + + this.OrderedPackageFacades = orderedFacades; + this.UsedRollbackBoundaries = usedBoundaries; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs new file mode 100644 index 00000000..f3afd64e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs @@ -0,0 +1,367 @@ +// 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.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class OrderSearchesCommand + { + public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public IDictionary> ExtensionSearchSymbolsByExtensionId { get; private set; } + + public IEnumerable OrderedSearchFacades { get; private set; } + + public void Execute() + { + this.ExtensionSearchSymbolsByExtensionId = new Dictionary>(); + this.OrderedSearchFacades = Array.Empty(); + + var searchSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + if (searchSymbols.Count == 0) + { + // Nothing to do! + return; + } + + var constraints = new Constraints(); + + // Add relational info to our data... + foreach (var searchRelationSymbol in this.Section.Symbols.OfType()) + { + constraints.AddConstraint(searchRelationSymbol.Id.Id, searchRelationSymbol.ParentSearchRef); + } + + this.FindCircularReference(constraints); + + if (this.Messaging.EncounteredError) + { + return; + } + + this.FlattenDependentReferences(constraints); + + // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting) + // We use a variation of Kahn (1962) algorithm as described in + // Wikipedia, with the additional criteria that start nodes are sorted + // lexicographically at each step to ensure a deterministic ordering + // based on 'after' dependencies and ID. + var sorter = new TopologicalSort(); + var sortedIds = sorter.Sort(searchSymbols.Keys, constraints); + + // Now, create the search facades with the searches in order... + (var orderedSearchFacades, var extensionSearchSymbolsByExtensionId) = this.OrderSearches(sortedIds, searchSymbols); + + this.OrderedSearchFacades = orderedSearchFacades; + this.ExtensionSearchSymbolsByExtensionId = extensionSearchSymbolsByExtensionId; + } + + /// + /// A dictionary of constraints, mapping an id to a list of ids. + /// + private class Constraints : Dictionary> + { + public void AddConstraint(string id, string afterId) + { + if (!this.ContainsKey(id)) + { + this.Add(id, new List()); + } + + // TODO: Show warning if a constraint is seen twice? + if (!this[id].Contains(afterId)) + { + this[id].Add(afterId); + } + } + + // TODO: Hide other Add methods? + } + + /// + /// Finds circular references in the constraints. + /// + /// Constraints to check. + /// This is not particularly performant, but it works. + private void FindCircularReference(Constraints constraints) + { + foreach (var id in constraints.Keys) + { + var seenIds = new List(); + + if (this.FindCircularReference(constraints, id, id, seenIds, out var chain)) + { + // We will show a separate message for every ID that's in + // the loop. We could bail after the first one, but then + // we wouldn't catch disjoint loops in a single run. + this.Messaging.Write(ErrorMessages.CircularSearchReference(chain)); + } + } + } + + /// + /// Recursive function that finds circular references in the constraints. + /// + /// Constraints to check. + /// The identifier currently being looking for. (Fixed across a given run.) + /// The idenifier curently being tested. + /// A list of identifiers seen, to ensure each identifier is only expanded once. + /// If a circular reference is found, will contain the chain of references. + /// True if a circular reference is found, false otherwise. + private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List seenIds, out string chain) + { + chain = null; + if (constraints.TryGetValue(currentId, out var afterList)) + { + foreach (string afterId in afterList) + { + if (afterId == checkId) + { + chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId); + return true; + } + + if (!seenIds.Contains(afterId)) + { + seenIds.Add(afterId); + if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain)) + { + chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain); + return true; + } + } + } + } + + return false; + } + + /// + /// Flattens any dependency chains to simplify reordering. + /// + /// + private void FlattenDependentReferences(Constraints constraints) + { + foreach (string id in constraints.Keys) + { + var flattenedIds = new List(); + this.AddDependentReferences(constraints, id, flattenedIds); + var constraintList = constraints[id]; + foreach (var flattenedId in flattenedIds) + { + if (!constraintList.Contains(flattenedId)) + { + constraintList.Add(flattenedId); + } + } + } + } + + /// + /// Adds dependent references to a list. + /// + /// + /// + /// + private void AddDependentReferences(Constraints constraints, string currentId, List seenIds) + { + if (constraints.TryGetValue(currentId, out var afterList)) + { + foreach (var afterId in afterList) + { + if (!seenIds.Contains(afterId)) + { + seenIds.Add(afterId); + this.AddDependentReferences(constraints, afterId, seenIds); + } + } + } + } + + /// + /// Reorder by topological sort + /// + /// + /// We use a variation of Kahn (1962) algorithm as described in + /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with + /// the additional criteria that start nodes are sorted lexicographically + /// at each step to ensure a deterministic ordering based on 'after' + /// dependencies and ID. + /// + private class TopologicalSort + { + private readonly List startIds = new List(); + private Constraints constraints; + + /// + /// Reorder by topological sort + /// + /// The complete list of IDs. + /// Constraints to use. + /// The topologically sorted list of IDs. + internal List Sort(IEnumerable allIds, Constraints constraints) + { + this.startIds.Clear(); + this.CopyConstraints(constraints); + + this.FindInitialStartIds(allIds); + + // We always create a new sortedId list, because we return it + // to the caller and don't know what its lifetime may be. + var sortedIds = new List(); + + while (this.startIds.Count > 0) + { + this.SortStartIds(); + + var currentId = this.startIds[0]; + sortedIds.Add(currentId); + this.startIds.RemoveAt(0); + + this.ResolveConstraint(currentId); + } + + return sortedIds; + } + + /// + /// Copies a Constraints set (to prevent modifying the incoming data). + /// + /// Constraints to copy. + private void CopyConstraints(Constraints constraints) + { + this.constraints = new Constraints(); + foreach (var id in constraints.Keys) + { + foreach (var afterId in constraints[id]) + { + this.constraints.AddConstraint(id, afterId); + } + } + } + + /// + /// Finds initial start IDs. (Those with no constraints.) + /// + /// The complete list of IDs. + private void FindInitialStartIds(IEnumerable allIds) + { + foreach (var id in allIds) + { + if (!this.constraints.ContainsKey(id)) + { + this.startIds.Add(id); + } + } + } + + /// + /// Sorts start IDs. + /// + private void SortStartIds() + { + this.startIds.Sort(); + } + + /// + /// Removes the resolved constraint and updates the list of startIds + /// with any now-valid (all constraints resolved) IDs. + /// + /// The ID to resolve from the set of constraints. + private void ResolveConstraint(string resolvedId) + { + var newStartIds = new List(); + + foreach (var id in this.constraints.Keys) + { + if (this.constraints[id].Contains(resolvedId)) + { + this.constraints[id].Remove(resolvedId); + + // If we just removed the last constraint for this + // ID, it is now a valid start ID. + if (this.constraints[id].Count == 0) + { + newStartIds.Add(id); + } + } + } + + foreach (var id in newStartIds) + { + this.constraints.Remove(id); + } + + this.startIds.AddRange(newStartIds); + } + } + + private (IEnumerable, Dictionary>) OrderSearches(IEnumerable sortedIds, Dictionary searchSymbolDictionary) + { + var orderedSearchFacades = new List(); + var extensionSearchSymbolsByExtensionId = new Dictionary>(); + + // 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. + var legacySearchesById = this.Section.Symbols + .Where(t => t.Definition.Type == SymbolDefinitionType.WixComponentSearch || + t.Definition.Type == SymbolDefinitionType.WixFileSearch || + t.Definition.Type == SymbolDefinitionType.WixProductSearch || + t.Definition.Type == SymbolDefinitionType.WixRegistrySearch) + .ToDictionary(t => t.Id.Id); + var setVariablesById = this.Section.Symbols + .OfType() + .ToDictionary(t => t.Id.Id); + var extensionSearchesById = this.Section.Symbols + .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag)) + .ToDictionary(t => t.Id.Id); + + foreach (var searchId in sortedIds) + { + var searchSymbol = searchSymbolDictionary[searchId]; + + if (legacySearchesById.TryGetValue(searchId, out var specificSearchSymbol)) + { + orderedSearchFacades.Add(new LegacySearchFacade(searchSymbol, specificSearchSymbol)); + } + else if (setVariablesById.TryGetValue(searchId, out var setVariableSymbol)) + { + orderedSearchFacades.Add(new SetVariableSearchFacade(searchSymbol, setVariableSymbol)); + } + else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchSymbol)) + { + orderedSearchFacades.Add(new ExtensionSearchFacade(searchSymbol)); + + if (!extensionSearchSymbolsByExtensionId.TryGetValue(searchSymbol.BundleExtensionRef, out var extensionSearchSymbols)) + { + extensionSearchSymbols = new List(); + extensionSearchSymbolsByExtensionId[searchSymbol.BundleExtensionRef] = extensionSearchSymbols; + } + extensionSearchSymbols.Add(extensionSearchSymbol); + } + else + { + this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchSymbol.SourceLineNumbers, searchId)); + } + } + + return (orderedSearchFacades, extensionSearchSymbolsByExtensionId.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value)); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs new file mode 100644 index 00000000..471262de --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs @@ -0,0 +1,25 @@ +// 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.Diagnostics; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class PackageFacade + { + public PackageFacade(WixBundlePackageSymbol packageSymbol, IntermediateSymbol specificPackageSymbol) + { + Debug.Assert(packageSymbol.Id.Id == specificPackageSymbol.Id.Id); + + this.PackageSymbol = packageSymbol; + this.SpecificPackageSymbol = specificPackageSymbol; + } + + public string PackageId => this.PackageSymbol.Id.Id; + + public WixBundlePackageSymbol PackageSymbol { get; } + + public IntermediateSymbol SpecificPackageSymbol { get; } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs new file mode 100644 index 00000000..8d8ea986 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs @@ -0,0 +1,39 @@ +// 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.Symbols; + + /// + /// Initializes package state from the Exe contents. + /// + internal class ProcessExePackageCommand + { + public ProcessExePackageCommand(PackageFacade facade, Dictionary payloadSymbols) + { + this.AuthoredPayloads = payloadSymbols; + this.Facade = facade; + } + + public Dictionary AuthoredPayloads { get; } + + public PackageFacade Facade { get; } + + /// + /// Processes the Exe packages to add properties and payloads from the Exe packages. + /// + public void Execute() + { + var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = packagePayload.Hash; + } + + this.Facade.PackageSymbol.Version = packagePayload.Version; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs new file mode 100644 index 00000000..99e2eda5 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -0,0 +1,558 @@ +// 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.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Core.Native.Msi; + + /// + /// Initializes package state from the MSI contents. + /// + internal class ProcessMsiPackageCommand + { + private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?"; + + public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary packagePayloads) + { + this.Messaging = serviceProvider.GetService(); + this.BackendHelper = serviceProvider.GetService(); + this.PathResolver = serviceProvider.GetService(); + + this.BackendExtensions = backendExtensions; + + this.PackagePayloads = packagePayloads; + this.Section = section; + this.Facade = facade; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IEnumerable BackendExtensions { get; } + + private Dictionary PackagePayloads { get; } + + private PackageFacade Facade { get; } + + private IntermediateSection Section { get; } + + /// + /// Processes the MSI packages to add properties and payloads from the MSI packages. + /// + public void Execute() + { + var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef]; + + var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol; + + var sourcePath = packagePayload.SourceFile.Path; + var longNamesInImage = false; + var compressed = false; + try + { + using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) + { + // Read data out of the msi database... + using (var sumInfo = new SummaryInformation(db)) + { + var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags); + var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages); + + // 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 == (fileAndElevateFlags & 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 == (fileAndElevateFlags & 2); + + // 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. + var perMachine = (0 == (fileAndElevateFlags & 8)); + var x64 = platformsAndLanguages.Contains("x64"); + + this.Facade.PackageSymbol.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + this.Facade.PackageSymbol.Win64 = x64; + } + + string packageName = null; + string packageDescription = null; + string allusers = null; + string fastInstall = null; + string systemComponent = null; + + using (var view = db.OpenView(PropertySqlQuery)) + { + packageName = ProcessMsiPackageCommand.GetProperty(view, "ProductName"); + packageDescription = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS"); + allusers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS"); + fastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL"); + systemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT"); + + msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode"); + msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode"); + msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer"); + msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"), CultureInfo.InvariantCulture); + msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion"); + } + + if (!this.BackendHelper.IsValidFourPartVersion(msiPackage.ProductVersion)) + { + // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? + string version = null; + var versionParts = msiPackage.ProductVersion.Split('.'); + var count = versionParts.Length; + if (0 < count) + { + version = versionParts[0]; + for (var i = 1; i < 4 && i < count; ++i) + { + version = String.Concat(version, ".", versionParts[i]); + } + } + + if (!String.IsNullOrEmpty(version) && this.BackendHelper.IsValidFourPartVersion(version)) + { + this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version)); + msiPackage.ProductVersion = version; + } + else + { + this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath)); + } + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion); + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) + { + this.Facade.PackageSymbol.DisplayName = packageName; + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) + { + this.Facade.PackageSymbol.Description = packageDescription; + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Version)) + { + this.Facade.PackageSymbol.Version = msiPackage.ProductVersion; + } + + var payloadNames = this.GetPayloadTargetNames(); + + var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); + + this.SetPerMachineAppropriately(allusers, msiPackage, sourcePath); + + // Ensure the MSI package is appropriately marked visible or not. + this.SetPackageVisibility(systemComponent, msiPackage, msiPropertyNames); + + // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. + if (!String.IsNullOrEmpty(fastInstall)) + { + this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7"); + } + + this.CreateRelatedPackages(db); + + // If feature selection is enabled, represent the Feature table in the manifest. + if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.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.PackageSymbol.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); + + // Add all dependency providers from the MSI. + this.ImportDependencyProviders(db, msiPackage); + } + } + catch (MsiException e) + { + this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, e.Message)); + } + } + + private ISet GetPayloadTargetNames() + { + var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); + + return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); + } + + private ISet GetMsiPropertyNames(string packageId) + { + var properties = this.Section.Symbols.OfType() + .Where(p => p.PackageRef == packageId) + .Select(p => p.Name); + + return new HashSet(properties, StringComparer.Ordinal); + } + + private void SetPerMachineAppropriately(string allusers, WixBundleMsiPackageSymbol msiPackage, string sourcePath) + { + if (msiPackage.ForcePerMachine) + { + if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); + this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. + } + + // Force ALLUSERS=1 via the MSI command-line. + this.AddMsiProperty(msiPackage, "ALLUSERS", "1"); + } + else + { + if (String.IsNullOrEmpty(allusers)) + { + // Not forced per-machine and no ALLUSERS property, flip back to per-user. + if (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); + this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.No; + } + } + else if (allusers.Equals("1", StringComparison.Ordinal)) + { + if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); + } + } + else if (allusers.Equals("2", StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) ? "machine" : "user")); + } + else + { + this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, allusers)); + } + } + } + + private void SetPackageVisibility(string systemComponent, WixBundleMsiPackageSymbol msiPackage, ISet msiPropertyNames) + { + // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. + if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) + { + var alreadyVisible = String.IsNullOrEmpty(systemComponent); + var visible = (this.Facade.PackageSymbol.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible; + + // If not already set to the correct visibility. + if (alreadyVisible != visible) + { + this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); + } + } + } + + private void CreateRelatedPackages(Database db) + { + // Represent the Upgrade table as related packages. + if (db.TableExists("Upgrade")) + { + using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) + { + foreach (var record in view.Records) + { + var recordAttributes = record.GetInteger(5); + + var attributes = WixBundleRelatedPackageAttributes.None; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0; + + this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + PackageRef = this.Facade.PackageId, + RelatedId = record.GetString(1), + MinVersion = record.GetString(2), + MaxVersion = record.GetString(3), + Languages = record.GetString(4), + Attributes = attributes, + }); + } + } + } + } + + private void CreateMsiFeatures(Database db) + { + if (db.TableExists("Feature")) + { + using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`")) + using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) + using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) + { + using (var featureRecord = new Record(1)) + using (var componentRecord = new Record(1)) + { + foreach (var allFeaturesResultRecord in allFeaturesView.Records) + { + var 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; + foreach (var componentResultRecord in featureView.Records) + { + var component = componentResultRecord.GetString(1); + componentRecord.SetString(1, component); + componentView.Execute(componentRecord); + + foreach (var fileResultRecord in componentView.Records) + { + var fileSize = fileResultRecord.GetString(1); + size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); + } + } + + this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, this.Facade.PackageId, featureName)) + { + PackageRef = this.Facade.PackageId, + Name = featureName, + Parent = allFeaturesResultRecord.GetString(2), + Title = allFeaturesResultRecord.GetString(3), + Description = allFeaturesResultRecord.GetString(4), + Display = allFeaturesResultRecord.GetInteger(5), + Level = allFeaturesResultRecord.GetInteger(6), + Directory = allFeaturesResultRecord.GetString(7), + Attributes = allFeaturesResultRecord.GetInteger(8), + Size = size + }); + } + } + } + } + } + + private void ImportExternalCabinetAsPayloads(Database db, WixBundlePayloadSymbol packagePayload, ISet payloadNames) + { + if (db.TableExists("Media")) + { + using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`")) + { + foreach (var cabinetRecord in view.Records) + { + var cabinet = cabinetRecord.GetString(1); + + 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... + var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); + + if (!payloadNames.Contains(cabinetName)) + { + var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); + var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers); + + this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Package, + ParentId = this.Facade.PackageId, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + + this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = cabinetName, + SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, + Compressed = packagePayload.Compressed, + UnresolvedSourceFile = cabinetName, + ContainerRef = packagePayload.ContainerRef, + ContentFile = true, + Packaging = packagePayload.Packaging, + ParentPackagePayloadRef = packagePayload.Id.Id, + }); + } + } + } + } + } + } + + private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, WixBundlePayloadSymbol packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) + { + long size = 0; + + if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File")) + { + var directories = new Dictionary(); + + // Load up the directory hash table so we will be able to resolve source paths + // for files in the MSI database. + using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + { + foreach (var record in view.Records) + { + var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage); + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); + + directories.Add(record.GetString(1), resolvedDirectory); + } + } + + // Resolve the source paths to external files and add each file size to the total + // install size of the package. + using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) + { + foreach (var record in view.Records) + { + // 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. + var compressionBit = record.GetInteger(4); + if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || + (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) + { + var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); + var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); + + if (!payloadNames.Contains(name)) + { + var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); + var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers); + + this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Package, + ParentId = this.Facade.PackageId, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + + this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = name, + SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, + Compressed = packagePayload.Compressed, + UnresolvedSourceFile = name, + ContainerRef = packagePayload.ContainerRef, + ContentFile = true, + Packaging = packagePayload.Packaging, + ParentPackagePayloadRef = packagePayload.Id.Id, + }); + } + } + + size += record.GetInteger(5); + } + } + } + + return size; + } + + private void AddMsiProperty(WixBundleMsiPackageSymbol msiPackage, string name, string value) + { + this.Section.AddSymbol(new WixBundleMsiPropertySymbol(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Section, msiPackage.Id.Id, name)) + { + PackageRef = msiPackage.Id.Id, + Name = name, + Value = value, + }); + } + + private void ImportDependencyProviders(Database db, WixBundleMsiPackageSymbol msiPackage) + { + if (db.TableExists("WixDependencyProvider")) + { + using (var view = db.OpenExecuteView("SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`")) + { + foreach (var record in view.Records) + { + var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1))); + + // Import the provider key and attributes. + this.Section.AddSymbol(new WixDependencyProviderSymbol(msiPackage.SourceLineNumbers, id) + { + ParentRef = msiPackage.Id.Id, + ProviderKey = record.GetString(2), + Version = record.GetString(3) ?? msiPackage.ProductVersion, + DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName, + Attributes = WixDependencyProviderAttributes.ProvidesAttributesImported | (WixDependencyProviderAttributes)record.GetInteger(5), + }); + } + } + } + } + + private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers) + { + var checkedPaths = new List(); + + foreach (var extension in this.BackendExtensions) + { + var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); + + if (!File.Exists(resolvedPath)) + { + checkedPaths.Add(resolvedPath); + this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); + } + + return resolvedPath; + } + + private static string GetProperty(View view, string property) + { + using (var queryRecord = new Record(1)) + { + queryRecord[1] = property; + + view.Execute(queryRecord); + + using (var record = view.Fetch()) + { + return record?.GetString(1); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..5f431b38 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs @@ -0,0 +1,183 @@ +// 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.Text; + using System.Xml; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + /// + /// Initializes package state from the Msp contents. + /// + internal class ProcessMspPackageCommand + { + private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?"; + private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings() + { + Encoding = new UTF8Encoding(false), + Indent = false, + NewLineChars = String.Empty, + NewLineHandling = NewLineHandling.Replace, + }; + + public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary payloadSymbols) + { + this.Messaging = messaging; + + this.AuthoredPayloads = payloadSymbols; + this.Section = section; + this.Facade = facade; + } + + public IMessaging Messaging { get; } + + public Dictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public IntermediateSection Section { get; } + + /// + /// Processes the Msp packages to add properties and payloads from the Msp packages. + /// + public void Execute() + { + var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + + var mspPackage = (WixBundleMspPackageSymbol)this.Facade.SpecificPackageSymbol; + + var sourcePath = packagePayload.SourceFile.Path; + + try + { + using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + { + // Read data out of the msp database... + using (var sumInfo = new SummaryInformation(db)) + { + var patchCode = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode); + mspPackage.PatchCode = patchCode.Substring(0, 38); + } + + using (var view = db.OpenView(PatchMetadataQuery)) + { + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) + { + this.Facade.PackageSymbol.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName"); + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) + { + this.Facade.PackageSymbol.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description"); + } + + mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName"); + } + } + + this.ProcessPatchXml(packagePayload, mspPackage, sourcePath); + } + catch (MsiException e) + { + this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); + return; + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = mspPackage.PatchCode; + } + } + + private void ProcessPatchXml(WixBundlePayloadSymbol packagePayload, WixBundleMspPackageSymbol mspPackage, string sourcePath) + { + var uniqueTargetCodes = new HashSet(); + + var patchXml = Installer.ExtractPatchXml(sourcePath); + + var doc = new XmlDocument(); + doc.LoadXml(patchXml); + + var 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. + var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); + var 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 + { + mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; + } + } + + var targetCode = targetCodeElement.InnerText; + + if (uniqueTargetCodes.Add(targetCode)) + { + this.Section.AddSymbol(new WixBundlePatchTargetCodeSymbol(packagePayload.SourceLineNumbers) + { + PackageRef = packagePayload.Id.Id, + TargetCode = targetCode, + Attributes = attributes + }); + } + } + + // Suppress patch sequence data for improved performance. + var root = doc.DocumentElement; + foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) + { + root.RemoveChild(node); + } + + // Save the XML as compact as possible. + using (var writer = new StringWriter()) + { + using (var xmlWriter = XmlWriter.Create(writer, XmlSettings)) + { + doc.WriteTo(xmlWriter); + } + + mspPackage.PatchXml = writer.ToString(); + } + } + + private static string GetPatchMetadataProperty(View view, string property) + { + using (var queryRecord = new Record(1)) + { + queryRecord[1] = property; + + view.Execute(queryRecord); + + using (var record = view.Fetch()) + { + return record?.GetString(1); + } + } + } + + private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value; + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs new file mode 100644 index 00000000..af4ab3a8 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Processes the Msu packages to add properties and payloads from the Msu packages. + /// + internal class ProcessMsuPackageCommand + { + public ProcessMsuPackageCommand(PackageFacade facade, Dictionary payloadSymbols) + { + this.AuthoredPayloads = payloadSymbols; + this.Facade = facade; + } + + public Dictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public void Execute() + { + var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = packagePayload.Hash; + } + + this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs new file mode 100644 index 00000000..fa70251a --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.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.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ProcessPayloadsCommand + { + public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) + { + this.BackendHelper = backendHelper; + this.PayloadHarvester = payloadHarvester; + this.Payloads = payloads; + this.DefaultPackaging = defaultPackaging; + this.LayoutDirectory = layoutDirectory; + } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable TrackedFiles { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IPayloadHarvester PayloadHarvester { get; } + + private IEnumerable Payloads { get; } + + private PackagingType DefaultPackaging { get; } + + private string LayoutDirectory { get; } + + public void Execute() + { + var fileTransfers = new List(); + var trackedFiles = new List(); + + foreach (var payload in this.Payloads) + { + payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name); + + // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden + // in the .wixlib). + var sourceFile = payload.SourceFile; + payload.ContentFile = sourceFile != null && !sourceFile.Embed; + + this.UpdatePayloadPackagingType(payload); + + if (!this.PayloadHarvester.HarvestStandardInformation(payload)) + { + // 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. + { + // External payloads need to be transfered. + if (PackagingType.External == payload.Packaging) + { + var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers); + fileTransfers.Add(transfer); + } + + if (payload.ContentFile) + { + trackedFiles.Add(this.BackendHelper.TrackFile(sourceFile.Path, TrackedFileType.Input, payload.SourceLineNumbers)); + } + } + } + + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + } + + private void UpdatePayloadPackagingType(WixBundlePayloadSymbol payload) + { + if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging) + { + if (!payload.Compressed.HasValue) + { + payload.Packaging = this.DefaultPackaging; + } + else if (payload.Compressed.Value) + { + payload.Packaging = PackagingType.Embedded; + } + else + { + payload.Packaging = PackagingType.External; + } + } + + // Embedded payloads that are not assigned a container already are placed in the default attached + // container. + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef)) + { + payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs new file mode 100644 index 00000000..854c84e0 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs @@ -0,0 +1,72 @@ +// 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 WixToolset.Data; + + internal static class BurnBackendErrors + { + public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the BA container. When extracting the container at runtime, the file will get overwritten.", payloadId, payloadName); + } + + public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error."); + } + + public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId) + { + return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId); + } + + public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber) + { + return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error."); + } + + public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.ExternalPayloadCollision, "The external {0} '{1}' has a duplicate Name '{2}'. When building the bundle or laying out the bundle, the file will get overwritten.", symbolName, payloadId, payloadName); + } + + public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error."); + } + + public static Message MultipleAttachedContainersUnsupported(SourceLineNumber sourceLineNumbers, string containerId) + { + return Message(sourceLineNumbers, Ids.MultipleAttachedContainersUnsupported, "Bundles don't currently support having more than one attached container. Either remove all authored attached containers to use the default attached container, or make sure all compressed payloads are included in this Container '{0}'.", containerId); + } + + public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId) + { + return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in package '{2}'. When caching the package, the file will get overwritten.", payloadId, payloadName, packageId); + } + + public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error."); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + DuplicateCacheIds = 8000, + DuplicateCacheIds2 = 8001, + BAContainerPayloadCollision = 8002, + BAContainerPayloadCollision2 = 8003, + ExternalPayloadCollision = 8004, + ExternalPayloadCollision2 = 8005, + PackageCachePayloadCollision = 8006, + PackageCachePayloadCollision2 = 8007, + MultipleAttachedContainersUnsupported = 8008, + } // last available is 8499. 8500 is BurnBackendWarnings. + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs new file mode 100644 index 00000000..03013a08 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendFactory.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 BurnBackendFactory : IBackendFactory + { + public bool TryCreateBackend(string outputType, string outputFile, 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/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs new file mode 100644 index 00000000..a0ffa1dc --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.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.Burn +{ + using WixToolset.Data; + + internal static class BurnBackendWarnings + { + public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", payloadId, payloadName); + } + + public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error."); + } + + public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string containerId) + { + return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + AttachedContainerPayloadCollision = 8500, + AttachedContainerPayloadCollision2 = 8501, + EmptyContainer = 8502, + } // last available is 8999. 9000 is VerboseMessages. + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs new file mode 100644 index 00000000..b34d12c1 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs @@ -0,0 +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.Core.Burn +{ + using System; + using WixToolset.Extensibility; + + internal class BurnExtensionFactory : IExtensionFactory + { + public bool TryCreateExtension(Type extensionType, out object extension) + { + extension = null; + + if (extensionType == typeof(IBackendFactory)) + { + extension = new BurnBackendFactory(); + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs new file mode 100644 index 00000000..e4d2b0c9 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs @@ -0,0 +1,214 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BurnBackendHelper : IInternalBurnBackendHelper + { + public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; + public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; + + private readonly IBackendHelper backendHelper; + + private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); + + private Dictionary BundleExtensionDataById { get; } = new Dictionary(); + + public BurnBackendHelper(IServiceProvider serviceProvider) + { + this.backendHelper = serviceProvider.GetService(); + } + + #region IBackendHelper interfaces + + public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); + + public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); + + public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); + + public string CreateGuid() => this.backendHelper.CreateGuid(); + + public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); + + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); + + public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); + + public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); + + public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); + + public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); + + public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); + + public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); + + public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); + + public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); + + public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); + + public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); + + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); + + #endregion + + #region IBurnBackendHelper interfaces + + public void AddBootstrapperApplicationData(string xml) + { + this.BootstrapperApplicationManifestData.AddXml(xml); + } + + public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) + { + this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace); + } + + public void AddBundleExtensionData(string extensionId, string xml) + { + var manifestData = this.GetBundleExtensionManifestData(extensionId); + manifestData.AddXml(xml); + } + + public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) + { + var manifestData = this.GetBundleExtensionManifestData(extensionId); + manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); + } + + #endregion + + #region IInternalBurnBackendHelper interfaces + + public void WriteBootstrapperApplicationData(XmlWriter writer) + { + this.BootstrapperApplicationManifestData.Write(writer); + } + + public void WriteBundleExtensionData(XmlWriter writer) + { + foreach (var kvp in this.BundleExtensionDataById) + { + this.WriteExtension(writer, kvp.Key, kvp.Value); + } + } + + #endregion + + private ManifestData GetBundleExtensionManifestData(string extensionId) + { + if (!this.backendHelper.IsValidIdentifier(extensionId)) + { + throw new ArgumentException($"'{extensionId}' is not a valid extensionId"); + } + + if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData)) + { + manifestData = new ManifestData(); + this.BundleExtensionDataById.Add(extensionId, manifestData); + } + + return manifestData; + } + + private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData) + { + writer.WriteStartElement("BundleExtension"); + + writer.WriteAttributeString("Id", extensionId); + + manifestData.Write(writer); + + writer.WriteEndElement(); + } + + private class ManifestData + { + public ManifestData() + { + this.Builder = new StringBuilder(); + } + + private StringBuilder Builder { get; } + + public void AddSymbol(IntermediateSymbol symbol, bool symbolIdIsIdAttribute, string ns) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're creating valid XML. + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, WriterSettings)) + { + writer.WriteStartElement(symbol.Definition.Name, ns); + + if (symbolIdIsIdAttribute && symbol.Id != null) + { + writer.WriteAttributeString("Id", symbol.Id.Id); + } + + foreach (var field in symbol.Fields) + { + if (!field.IsNull()) + { + writer.WriteAttributeString(field.Definition.Name, field.AsString()); + } + } + + writer.WriteEndElement(); + } + + this.AddXml(sb.ToString()); + } + + public void AddXml(string xml) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're given valid XML. + var sb = new StringBuilder(); + using (var xmlWriter = XmlWriter.Create(sb, WriterSettings)) + { + AddManifestDataFromString(xmlWriter, xml); + } + this.Builder.Append(sb.ToString()); + } + + public void Write(XmlWriter writer) + { + AddManifestDataFromString(writer, this.Builder.ToString()); + } + + private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml) + { + using (var stringReader = new StringReader(xml)) + using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings)) + { + while (xmlReader.MoveToContent() != XmlNodeType.None) + { + xmlWriter.WriteNode(xmlReader, false); + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs new file mode 100644 index 00000000..9ef91028 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.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.ExtensibilityServices +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data.Symbols; + + internal class PayloadHarvester : IPayloadHarvester + { + private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); + + /// + public bool HarvestStandardInformation(WixBundlePayloadSymbol payload) + { + var filePath = payload.SourceFile?.Path; + + if (String.IsNullOrEmpty(filePath)) + { + return false; + } + + this.UpdatePayloadFileInformation(payload, filePath); + + this.UpdatePayloadVersionInformation(payload, filePath); + + return true; + } + + private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath) + { + var fileInfo = new FileInfo(filePath); + + if (null != fileInfo) + { + payload.FileSize = fileInfo.Length; + + payload.Hash = BundleHashAlgorithm.Hash(fileInfo); + } + else + { + payload.FileSize = 0; + } + } + + private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath) + { + var versionInfo = FileVersionInfo.GetVersionInfo(filePath); + + if (null != versionInfo) + { + // Use the fixed version info block for the file since the resource text may not be a dotted quad. + var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); + + if (PayloadHarvester.EmptyVersion != version) + { + payload.Version = version.ToString(); + } + + payload.Description = versionInfo.FileDescription; + payload.DisplayName = versionInfo.ProductName; + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs new file mode 100644 index 00000000..59c4f20f --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.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.Burn +{ + using System.Xml; + using WixToolset.Extensibility.Services; + + internal interface IInternalBurnBackendHelper : IBurnBackendHelper + { + void WriteBootstrapperApplicationData(XmlWriter writer); + + void WriteBundleExtensionData(XmlWriter writer); + } +} diff --git a/src/wix/WixToolset.Core.Burn/ISearchFacade.cs b/src/wix/WixToolset.Core.Burn/ISearchFacade.cs new file mode 100644 index 00000000..b9ad8649 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ISearchFacade.cs @@ -0,0 +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.Core.Burn +{ + using System.Xml; + + internal interface ISearchFacade + { + /// + /// Writes the search to the Burn manifest. + /// + /// + void WriteXml(XmlTextWriter writer); + } +} diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs new file mode 100644 index 00000000..b466d0de --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.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.Inscribe +{ + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Native; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class InscribeBundleCommand + { + public InscribeBundleCommand(IInscribeContext context) + { + this.Context = context; + + this.Messaging = context.ServiceProvider.GetService(); + } + + private IInscribeContext Context { get; } + + public IMessaging Messaging { get; } + + public bool Execute() + { + var inscribed = false; + var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); + + using (var reader = BurnReader.Open(this.Context.InputFilePath)) + { + FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false); + + // 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 (var writer = BurnWriter.Open(this.Messaging, tempFile)) + { + writer.RememberThenResetSignature(); + writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); + inscribed = true; + } + } + } + + Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); + + FileSystem.MoveFile(tempFile, this.Context.OutputFile); + + return inscribed; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs new file mode 100644 index 00000000..a6789796 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs @@ -0,0 +1,63 @@ +// 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.Core.Native; + using WixToolset.Extensibility.Data; + + internal class InscribeBundleEngineCommand + { + public InscribeBundleEngineCommand(IInscribeContext context) + { + this.IntermediateFolder = context.IntermediateFolder; + this.InputFilePath = context.InputFilePath; + this.OutputFile = context.OutputFile; + } + + private string IntermediateFolder { get; } + + private string InputFilePath { get; } + + private string OutputFile { get; } + + public bool Execute() + { + var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); + + using (var reader = BurnReader.Open(this.InputFilePath)) + using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) + { + reader.Stream.Seek(0, SeekOrigin.Begin); + + var buffer = new byte[4 * 1024]; + var total = 0; + var 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.OutputFile)); + + FileSystem.MoveFile(tempFile, this.OutputFile); + + return true; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs b/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs new file mode 100644 index 00000000..1bafa46e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.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.Burn.Interfaces +{ + using System.Diagnostics; + using WixToolset.Data.Symbols; + + /// + /// Service for harvesting payload information. + /// + public interface IPayloadHarvester + { + /// + /// Uses to: + /// update from file contents, + /// update from file size, and + /// update , , and from . + /// + /// The symbol to update. + /// Whether the symbol had a source file specified. + bool HarvestStandardInformation(WixBundlePayloadSymbol payload); + } +} diff --git a/src/wix/WixToolset.Core.Burn/RowIndexedList.cs b/src/wix/WixToolset.Core.Burn/RowIndexedList.cs new file mode 100644 index 00000000..fd762a24 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/RowIndexedList.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.Core.Burn +{ + using System; + using System.Collections.Generic; + using WixToolset.Data.WindowsInstaller; + + /// + /// A list of rows indexed by their primary key. Unlike a RowDictionary + /// this indexed list will track rows in their added order and will allow rows with + /// duplicate keys to be added to the list, although only the first row will be indexed. + /// + internal sealed class RowIndexedList : IList where T : Row + { + private readonly Dictionary index; + private readonly List rows; + private readonly List duplicates; + + /// + /// Creates an empty . + /// + public RowIndexedList() + { + this.index = new Dictionary(StringComparer.InvariantCulture); + this.rows = new List(); + this.duplicates = new List(); + } + + /// + /// Creates and populates a with the rows from the given enumerator. + /// + /// Rows to index. + public RowIndexedList(IEnumerable rows) + : this() + { + foreach (var row in rows) + { + this.Add(row); + } + } + + /// + /// Creates and populates a with the rows from the given . + /// + /// The table to index. + /// + /// Rows added to the index are not automatically added to the given . + /// + public RowIndexedList(Table table) + : this() + { + if (null != table) + { + foreach (T row in table.Rows) + { + this.Add(row); + } + } + } + + /// + /// Gets the duplicates in the list. + /// + public IEnumerable Duplicates { get { return this.duplicates; } } + + /// + /// Gets the row by integer key. + /// + /// Integer key to look up. + /// Row or null if key is not found. + public T Get(int key) + { + return this.Get(key.ToString()); + } + + /// + /// Gets the row by string key. + /// + /// String key to look up. + /// Row or null if key is not found. + public T Get(string key) + { + return this.TryGet(key, out var result) ? result : null; + } + + /// + /// Gets the row by string key if it exists. + /// + /// Key of row to get. + /// Row found. + /// True if key was found otherwise false. + public bool TryGet(string key, out T row) + { + return this.index.TryGetValue(key, out row); + } + + /// + /// Tries to add a row as long as it would not create a duplicate. + /// + /// Row to add. + /// True if the row as added otherwise false. + public bool TryAdd(T row) + { + try + { + this.index.Add(row.GetKey(), row); + } + catch (ArgumentException) // if the key already exists, bail. + { + return false; + } + + this.rows.Add(row); + return true; + } + + /// + /// Adds a row to the list. If a row with the same key is already index, the row is + /// is not in the index but will still be part of the list and added to the duplicates + /// list. + /// + /// + public void Add(T row) + { + this.rows.Add(row); + try + { + this.index.Add(row.GetKey(), row); + } + catch (ArgumentException) // if the key already exists, we have a duplicate. + { + this.duplicates.Add(row); + } + } + + /// + /// Gets the index of a row. + /// + /// Iterates through the list of rows to find the index of a particular row. + /// Index of row or -1 if not found. + public int IndexOf(T row) + { + return this.rows.IndexOf(row); + } + + /// + /// Inserts a row at a particular index of the list. + /// + /// Index to insert the row after. + /// Row to insert. + public void Insert(int index, T row) + { + this.rows.Insert(index, row); + try + { + this.index.Add(row.GetKey(), row); + } + catch (ArgumentException) // if the key already exists, we have a duplicate. + { + this.duplicates.Add(row); + } + } + + /// + /// Removes a row from a particular index. + /// + /// Index to remove the row at. + public void RemoveAt(int index) + { + var row = this.rows[index]; + + this.rows.RemoveAt(index); + + if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) + { + this.index.Remove(row.GetKey()); + } + else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). + { + this.duplicates.Remove(row); + } + } + + /// + /// Gets or sets a row at the specified index. + /// + /// Index to get the row. + /// Row at specified index. + public T this[int index] + { + get + { + return this.rows[index]; + } + set + { + this.rows[index] = value; + try + { + this.index.Add(value.GetKey(), value); + } + catch (ArgumentException) // if the key already exists, we have a duplicate. + { + this.duplicates.Add(value); + } + } + } + + /// + /// Empties the list and it's index. + /// + public void Clear() + { + this.index.Clear(); + this.rows.Clear(); + this.duplicates.Clear(); + } + + /// + /// Searches the list for a row without using the index. + /// + /// Row to look for in the list. + /// True if the row is in the list, otherwise false. + public bool Contains(T row) + { + return this.rows.Contains(row); + } + + /// + /// Copies the rows of the list to an array. + /// + /// Array to copy the list into. + /// Index to start copying at. + public void CopyTo(T[] array, int arrayIndex) + { + this.rows.CopyTo(array, arrayIndex); + } + + /// + /// Number of rows in the list. + /// + public int Count + { + get { return this.rows.Count; } + } + + /// + /// Indicates whether the list is read-only. Always false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Removes a row from the list. Indexed rows will be removed but the colleciton will NOT + /// promote duplicates to the index automatically. The duplicate would also need to be removed + /// and re-added to be indexed. + /// + /// + /// + public bool Remove(T row) + { + var removed = this.rows.Remove(row); + if (removed) + { + if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) + { + this.index.Remove(row.GetKey()); + } + else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). + { + this.duplicates.Remove(row); + } + } + + return removed; + } + + /// + /// Gets an enumerator over the whole list. + /// + /// List enumerator. + public IEnumerator GetEnumerator() + { + return this.rows.GetEnumerator(); + } + + /// + /// Gets an untyped enumerator over the whole list. + /// + /// Untyped list enumerator. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.rows.GetEnumerator(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj new file mode 100644 index 00000000..f2da8a50 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -0,0 +1,39 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Core Burn + WiX Toolset Core Burn + embedded + true + true + + + + + <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..58076d5e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.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.Core.Burn +{ + using System; + using System.Collections.Generic; + using WixToolset.Core.Burn.ExtensibilityServices; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Extensibility.Services; + + /// + /// Extensions methods for adding Burn services. + /// + public static class WixToolsetCoreServiceProviderExtensions + { + /// + /// Adds Burn Services. + /// + /// + /// + public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) + { + AddServices(coreProvider); + + var extensionManager = coreProvider.GetService(); + extensionManager.Add(typeof(BurnExtensionFactory).Assembly); + + return coreProvider; + } + + private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) + { + // Singletons. + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new BurnBackendHelper(provider))); + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new PayloadHarvester())); + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, provider.GetService())); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs b/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs new file mode 100644 index 00000000..5567541c --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.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.ExtensionCache +{ + internal class CachedExtension + { + public CachedExtension(string id, string version, bool damaged) + { + this.Id = id; + this.Version = version; + this.Damaged = damaged; + } + + public string Id { get; } + + public string Version { get; } + + public bool Damaged { get; } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs new file mode 100644 index 00000000..256eeb0b --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs @@ -0,0 +1,248 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using NuGet.Common; + using NuGet.Configuration; + using NuGet.Credentials; + using NuGet.Packaging; + using NuGet.Protocol; + using NuGet.Protocol.Core.Types; + using NuGet.Versioning; + + /// + /// Extension cache manager. + /// + internal class ExtensionCacheManager + { + public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder(); + + public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions"); + + public string GlobalCacheFolder() + { + var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Combine(baseFolder, ".wix", "extensions"); + } + + public async Task AddAsync(bool global, string extension, CancellationToken cancellationToken) + { + if (String.IsNullOrEmpty(extension)) + { + throw new ArgumentNullException(nameof(extension)); + } + + (var extensionId, var extensionVersion) = ParseExtensionReference(extension); + + var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken); + + return result; + } + + public Task RemoveAsync(bool global, string extension, CancellationToken cancellationToken) + { + if (String.IsNullOrEmpty(extension)) + { + throw new ArgumentNullException(nameof(extension)); + } + + (var extensionId, var extensionVersion) = ParseExtensionReference(extension); + + var cacheFolder = this.CacheFolder(global); + + cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); + + if (Directory.Exists(cacheFolder)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Directory.Delete(cacheFolder, true); + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + + public Task> ListAsync(bool global, string extension, CancellationToken cancellationToken) + { + var found = new List(); + + (var extensionId, var extensionVersion) = ParseExtensionReference(extension); + + var cacheFolder = this.CacheFolder(global); + + var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); + + if (!Directory.Exists(searchFolder)) + { + } + else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension. + { + var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion); + found.Add(new CachedExtension(extensionId, extensionVersion, !present)); + } + else // looking for all versions of an extension or all versions of all extensions. + { + IEnumerable foundExtensionIds; + + if (String.IsNullOrEmpty(extensionId)) + { + // Looking for all versions of all extensions. + foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList(); + } + else + { + // Looking for all versions of a single extension. + var extensionFolder = Path.Combine(cacheFolder, extensionId); + foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty(); + } + + foreach (var foundExtensionId in foundExtensionIds) + { + var extensionFolder = Path.Combine(cacheFolder, foundExtensionId); + + foreach (var folder in Directory.GetDirectories(extensionFolder)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var foundExtensionVersion = Path.GetFileName(folder); + + if (!NuGetVersion.TryParse(foundExtensionVersion, out _)) + { + continue; + } + + var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion); + found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present)); + } + } + } + + return Task.FromResult((IEnumerable)found); + } + + private async Task DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken) + { + var logger = NullLogger.Instance; + + DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false); + + var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory); + var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled); + + using (var cache = new SourceCacheContext()) + { + PackageSource versionSource = null; + + var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version); + + if (nugetVersion is null) + { + foreach (var source in sources) + { + var repository = Repository.Factory.GetCoreV3(source.Source); + var resource = await repository.GetResourceAsync(); + + var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken); + foreach (var availableVersion in availableVersions) + { + if (nugetVersion is null || nugetVersion < availableVersion) + { + nugetVersion = availableVersion; + versionSource = source; + } + } + } + + if (nugetVersion is null) + { + return false; + } + } + + var searchSources = versionSource is null ? sources : new[] { versionSource }; + + var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString()); + + foreach (var source in searchSources) + { + var repository = Repository.Factory.GetCoreV3(source.Source); + var resource = await repository.GetResourceAsync(); + + using (var stream = new MemoryStream()) + { + var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken); + + if (downloaded) + { + stream.Position = 0; + + using (var archive = new PackageArchiveReader(stream)) + { + var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase); + await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken); + } + + return true; + } + } + } + } + + return false; + } + + private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath); + + private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference) + { + var extensionId = extensionReference ?? String.Empty; + var extensionVersion = String.Empty; + + var index = extensionId.LastIndexOf('/'); + if (index > 0) + { + extensionVersion = extensionReference.Substring(index + 1); + extensionId = extensionReference.Substring(0, index); + + if (!NuGetVersion.TryParse(extensionVersion, out _)) + { + throw new ArgumentException($"Invalid extension version in {extensionReference}"); + } + + if (String.IsNullOrEmpty(extensionId)) + { + throw new ArgumentException($"Invalid extension id in {extensionReference}"); + } + } + + return (extensionId, extensionVersion); + } + + private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion) + { + var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools"); + if (!Directory.Exists(toolsFolder)) + { + return false; + } + + var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll"); + + var present = File.Exists(extensionAssembly); + if (!present) + { + extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe"); + present = File.Exists(extensionAssembly); + } + + return present; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs new file mode 100644 index 00000000..94ee4f22 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs @@ -0,0 +1,181 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Extension cache manager command. + /// + internal class ExtensionCacheManagerCommand : ICommandLineCommand + { + private enum CacheSubcommand + { + Add, + Remove, + List + } + + public ExtensionCacheManagerCommand(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + this.ExtensionReferences = new List(); + } + + private IMessaging Messaging { get; } + + public bool ShowLogo { get; private set; } + + public bool StopParsing { get; private set; } + + private bool ShowHelp { get; set; } + + private bool Global { get; set; } + + private CacheSubcommand? Subcommand { get; set; } + + private List ExtensionReferences { get; } + + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (this.ShowHelp || !this.Subcommand.HasValue) + { + DisplayHelp(); + return 1; + } + + var success = false; + var cacheManager = new ExtensionCacheManager(); + + switch (this.Subcommand) + { + case CacheSubcommand.Add: + success = await this.AddExtensions(cacheManager, cancellationToken); + break; + + case CacheSubcommand.Remove: + success = await this.RemoveExtensions(cacheManager, cancellationToken); + break; + + case CacheSubcommand.List: + success = await this.ListExtensions(cacheManager, cancellationToken); + break; + } + + return success ? 0 : 2; + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + if (!parser.IsSwitch(argument)) + { + if (!this.Subcommand.HasValue) + { + if (!Enum.TryParse(argument, true, out CacheSubcommand subcommand)) + { + return false; + } + + this.Subcommand = subcommand; + } + else + { + this.ExtensionReferences.Add(argument); + } + + return true; + } + + var parameter = argument.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "-help": + this.ShowHelp = true; + this.ShowLogo = true; + this.StopParsing = true; + return true; + + case "nologo": + case "-nologo": + this.ShowLogo = false; + return true; + + case "g": + case "-global": + this.Global = true; + return true; + } + + return false; + } + + private async Task AddExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) + { + var success = false; + + foreach (var extensionRef in this.ExtensionReferences) + { + var added = await cacheManager.AddAsync(this.Global, extensionRef, cancellationToken); + success |= added; + } + + return success; + } + + private async Task RemoveExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) + { + var success = false; + + foreach (var extensionRef in this.ExtensionReferences) + { + var removed = await cacheManager.RemoveAsync(this.Global, extensionRef, cancellationToken); + success |= removed; + } + + return success; + } + + private async Task ListExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) + { + var found = false; + var extensionRef = this.ExtensionReferences.FirstOrDefault(); + + var extensions = await cacheManager.ListAsync(this.Global, extensionRef, cancellationToken); + + foreach (var extension in extensions) + { + this.Messaging.Write($"{extension.Id} {extension.Version}{(extension.Damaged ? " (damaged)" : String.Empty)}"); + found = true; + } + + return found; + } + + private static void DisplayHelp() + { + Console.WriteLine(); + Console.WriteLine("Usage: wix extension add|remove|list [extensionRef]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -h|--help Show command line help."); + Console.WriteLine(" -g|--global Add/remove the extension for the current user."); + Console.WriteLine(" --nologo Suppress displaying the logo information."); + Console.WriteLine(); + Console.WriteLine("Commands:"); + Console.WriteLine(); + Console.WriteLine(" add Add extension to the cache."); + Console.WriteLine(" list List extensions in the cache."); + Console.WriteLine(" remove Remove extension from the cache."); + Console.WriteLine(); + Console.WriteLine(" extensionRef format: extensionId/version (the version is optional)"); + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs new file mode 100644 index 00000000..2a603adf --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs @@ -0,0 +1,41 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Parses the "extension" command-line command. See ExtensionCacheManagerCommand + /// for the bulk of the command-line processing. + /// + internal class ExtensionCacheManagerExtensionCommandLine : BaseExtensionCommandLine + { + public ExtensionCacheManagerExtensionCommandLine(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + public override IReadOnlyCollection CommandLineSwitches => new ExtensionCommandLineSwitch[] + { + new ExtensionCommandLineSwitch { Switch = "extension", Description = "Manage extension cache." }, + }; + + public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) + { + command = null; + + if ("extension".Equals(argument, StringComparison.OrdinalIgnoreCase)) + { + command = new ExtensionCacheManagerCommand(this.ServiceProvider); + } + + return command != null; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs new file mode 100644 index 00000000..c38e5c70 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.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.ExtensionCache +{ + using System; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExtensionCacheManagerExtensionFactory : IExtensionFactory + { + public ExtensionCacheManagerExtensionFactory(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + public bool TryCreateExtension(Type extensionType, out object extension) + { + extension = null; + + if (extensionType == typeof(IExtensionCommandLine)) + { + extension = new ExtensionCacheManagerExtensionCommandLine(this.ServiceProvider); + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj b/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj new file mode 100644 index 00000000..1383305c --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj @@ -0,0 +1,29 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Extension Cache + WiX Toolset Extension Cache + embedded + true + true + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..424fc469 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility.Services; + + /// + /// Extensions methods for adding ExtensionCache services. + /// + public static class WixToolsetCoreServiceProviderExtensions + { + /// + /// Adds ExtensionCache services. + /// + /// + /// + public static IWixToolsetCoreServiceProvider AddExtensionCacheManager(this IWixToolsetCoreServiceProvider coreProvider) + { + var extensionManager = coreProvider.GetService(); + extensionManager.Add(typeof(ExtensionCacheManagerExtensionFactory).Assembly); + + coreProvider.AddService(CreateExtensionCacheManager); + return coreProvider; + } + + private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary singletons) + { + var extensionCacheManager = new ExtensionCacheManager(); + singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager); + + return extensionCacheManager; + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs new file mode 100644 index 00000000..8c9f31e6 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -0,0 +1,139 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + + /// + /// Class to extract bundle contents for testing. + /// + public class BundleExtractor + { + /// + /// Extracts the BA container. + /// + /// + /// Path to the bundle. + /// Path to extract to. + /// Temp path for extraction. + /// + public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + var result = new ExtractBAContainerResult(); + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); + } + + if (result.Success) + { + result.ManifestDocument = LoadBurnManifest(destinationFolderPath); + result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); + + result.BADataDocument = LoadBAData(destinationFolderPath); + result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); + + result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); + result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); + } + + return result; + } + + /// + /// Extracts the attached container. + /// + /// + /// Path to the bundle. + /// Path to extract to. + /// Temp path for extraction. + /// True if there was an attached container. + public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath); + } + } + + /// + /// Gets an for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace. + /// + /// + /// + /// + public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); + return namespaceManager; + } + + /// + /// Gets an for BundleExtensionData.xml with the given prefix assigned to the root namespace. + /// + /// + /// + /// + public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); + return namespaceManager; + } + + /// + /// Gets an for the Burn manifest.xml with the given prefix assigned to the root namespace. + /// + /// + /// + /// + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); + return namespaceManager; + } + + /// + /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. + /// + /// + /// + public static XmlDocument LoadBAData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); + return document; + } + + /// + /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. + /// + /// + /// + public static XmlDocument LoadBundleExtensionData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); + return document; + } + + /// + /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. + /// + /// + /// + public static XmlDocument LoadBurnManifest(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, "manifest.xml")); + return document; + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs new file mode 100644 index 00000000..277861ff --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.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.Core.TestPackage +{ + using System.IO; + using System.Xml; + using Xunit; + + /// + /// The result of extracting the BA container. + /// + public class ExtractBAContainerResult + { + /// + /// for BundleExtensionData.xml. + /// + public XmlDocument BundleExtensionDataDocument { get; set; } + + /// + /// for BundleExtensionData.xml. + /// + public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } + + /// + /// for BootstrapperApplicationData.xml. + /// + public XmlDocument BADataDocument { get; set; } + + /// + /// for BootstrapperApplicationData.xml. + /// + public XmlNamespaceManager BADataNamespaceManager { get; set; } + + /// + /// for the Burn manifest.xml. + /// + public XmlDocument ManifestDocument { get; set; } + + /// + /// for the Burn manifest.xml. + /// + public XmlNamespaceManager ManifestNamespaceManager { get; set; } + + /// + /// Whether extraction succeeded. + /// + public bool Success { get; set; } + + /// + /// + /// + /// + public ExtractBAContainerResult AssertSuccess() + { + Assert.True(this.Success); + return this; + } + + /// + /// Returns the relative path of the BA entry point dll in the given folder. + /// + /// + /// + public string GetBAFilePath(string extractedBAContainerFolderPath) + { + var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); + var baPayload = uxPayloads[0]; + var relativeBAPath = baPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); + } + + /// + /// Returns the relative path of the BundleExtension entry point dll in the given folder. + /// + /// + /// + /// + public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) + { + var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); + var bextPayload = uxPayloads[0]; + var relativeBextPath = bextPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); + } + + /// + /// + /// + /// elements must have the 'ba' prefix + /// + public XmlNodeList SelectBADataNodes(string xpath) + { + return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); + } + + /// + /// + /// + /// elements must have the 'be' prefix + /// + public XmlNodeList SelectBundleExtensionDataNodes(string xpath) + { + return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); + } + + /// + /// + /// + /// elements must have the 'burn' prefix + /// + public XmlNodeList SelectManifestNodes(string xpath) + { + return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs b/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs new file mode 100644 index 00000000..7040fe82 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using WixToolset.Data; +using WixToolset.Extensibility; +using WixToolset.Extensibility.Services; + +namespace WixToolset.Core.TestPackage +{ + /// + /// An that simply stores all the messages. + /// + public sealed class TestMessageListener : IMessageListener + { + /// + /// All messages that have been received. + /// + public List Messages { get; } = new List(); + + /// + /// + /// + public string ShortAppName => "TEST"; + + /// + /// + /// + public string LongAppName => "Test"; + + /// + /// Stores the message in . + /// + /// + public void Write(Message message) + { + this.Messages.Add(message); + } + + /// + /// Stores the message in . + /// + /// + public void Write(string message) + { + this.Messages.Add(new Message(null, MessageLevel.Information, 0, message)); + } + + /// + /// Always returns defaultMessageLevel. + /// + /// + /// + /// + /// + public MessageLevel CalculateMessageLevel(IMessaging messaging, Message message, MessageLevel defaultMessageLevel) => defaultMessageLevel; + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunner.cs b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs new file mode 100644 index 00000000..ed7c49b8 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs @@ -0,0 +1,88 @@ +// 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.TestPackage +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Core.Burn; + using WixToolset.Core.WindowsInstaller; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// Utility class to emulate wix.exe with standard backends. + /// + public static class WixRunner + { + /// + /// Emulates calling wix.exe with standard backends. + /// + /// + /// + /// + /// + public static int Execute(string[] args, out List messages, bool warningsAsErrors = true) + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var task = Execute(args, serviceProvider, out messages, warningsAsErrors: warningsAsErrors); + return task.Result; + } + + /// + /// Emulates calling wix.exe with standard backends. + /// This overload always treats warnings as errors. + /// + /// + /// + public static WixRunnerResult Execute(params string[] args) + { + return Execute(true, args); + } + + /// + /// Emulates calling wix.exe with standard backends. + /// + /// + /// + /// + public static WixRunnerResult Execute(bool warningsAsErrors, params string[] args) + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var exitCode = Execute(args, serviceProvider, out var messages, warningsAsErrors: warningsAsErrors); + return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() }; + } + + /// + /// Emulates calling wix.exe with standard backends. + /// + /// + /// + /// + /// + /// + public static Task Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List messages, bool warningsAsErrors = true) + { + coreProvider.AddWindowsInstallerBackend() + .AddBundleBackend(); + + var listener = new TestMessageListener(); + + messages = listener.Messages; + + var messaging = coreProvider.GetService(); + messaging.SetListener(listener); + + var arguments = new List(args); + if (warningsAsErrors) + { + arguments.Add("-wx"); + } + + var commandLine = coreProvider.GetService(); + var command = commandLine.CreateCommand(arguments.ToArray()); + return command?.ExecuteAsync(CancellationToken.None) ?? Task.FromResult(1); + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs new file mode 100644 index 00000000..6a3d714c --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.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.TestPackage +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using Xunit; + + /// + /// The result of an Execute method of . + /// + public class WixRunnerResult + { + /// + /// ExitCode for the operation. + /// + public int ExitCode { get; set; } + + /// + /// Messages from the operation. + /// + public Message[] Messages { get; set; } + + /// + /// + /// + /// + public WixRunnerResult AssertSuccess() + { + AssertSuccess(this.ExitCode, this.Messages); + return this; + } + + /// + /// + /// + /// + /// + public static void AssertSuccess(int exitCode, IEnumerable messages) + { + Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n"); + } + + private static IEnumerable FormatMessages(IEnumerable messages) + { + foreach (var message in messages) + { + var filename = message.SourceLineNumbers?.FileName ?? "TEST"; + var line = message.SourceLineNumbers?.LineNumber ?? -1; + var type = message.Level.ToString().ToLowerInvariant(); + + if (line > 0) + { + filename = String.Concat(filename, "(", line, ")"); + } + + yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString()); + } + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj new file mode 100644 index 00000000..b64b4075 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj @@ -0,0 +1,34 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Internal WiX Toolset Test Package + embedded + true + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs new file mode 100644 index 00000000..f4966f74 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs @@ -0,0 +1,90 @@ +// 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.TestPackage +{ + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + + /// + /// Utility class to help compare XML in tests using string comparisons by using single quotes and stripping all namespaces. + /// + public static class XmlNodeExtensions + { + /// + /// Returns the node's outer XML using single quotes and stripping all namespaces. + /// + /// + /// Attributes for which the value should be set to '*'. + /// + public static string GetTestXml(this XmlNode node, Dictionary> ignoredAttributesByElementName = null) + { + return node.OuterXml.GetTestXml(ignoredAttributesByElementName); + } + + /// + /// Returns the XML using single quotes and stripping all namespaces. + /// + /// + /// Attributes for which the value should be set to '*'. + /// + public static string GetTestXml(this string xml, Dictionary> ignoredAttributesByElementName = null) + { + string formattedXml; + using (var sw = new StringWriter()) + using (var writer = new TestXmlWriter(sw)) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + + if (ignoredAttributesByElementName != null) + { + HandleIgnoredAttributes(doc, ignoredAttributesByElementName); + } + + doc.Save(writer); + formattedXml = sw.ToString(); + } + + return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); + } + + private static void HandleIgnoredAttributes(XmlNode node, Dictionary> ignoredAttributesByElementName) + { + if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) + { + foreach (var ignoredAttribute in ignoredAttributes) + { + var attribute = node.Attributes[ignoredAttribute]; + if (attribute != null) + { + attribute.Value = "*"; + } + } + } + + if (node.ChildNodes != null) + { + foreach (XmlNode childNode in node.ChildNodes) + { + HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); + } + } + } + + private class TestXmlWriter : XmlTextWriter + { + public TestXmlWriter(TextWriter w) + : base(w) + { + this.QuoteChar = '\''; + } + + public override void WriteStartDocument() + { + //OmitXmlDeclaration + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs new file mode 100644 index 00000000..cbba6030 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs @@ -0,0 +1,52 @@ +// 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 WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Add back possibly suppressed sequence tables since all sequence tables must be present + /// for the merge process to work. We'll drop the suppressed sequence tables again as + /// necessary. + /// + internal class AddBackSuppressedSequenceTablesCommand + { + public AddBackSuppressedSequenceTablesCommand(WindowsInstallerData output, TableDefinitionCollection tableDefinitions) + { + this.Output = output; + this.TableDefinitions = tableDefinitions; + } + + private WindowsInstallerData Output { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + public IEnumerable SuppressedTableNames { get; private set; } + + public IEnumerable Execute() + { + var suppressedTableNames = new HashSet(); + + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = sequence.WindowsInstallerTableName(); + var sequenceTable = this.Output.Tables[sequenceTableName]; + + if (null == sequenceTable) + { + sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); + } + + if (0 == sequenceTable.Rows.Count) + { + suppressedTableNames.Add(sequenceTableName); + } + } + + return this.SuppressedTableNames = suppressedTableNames; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs new file mode 100644 index 00000000..c4fddb3e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Add CreateFolder symbols, if not already present, for null-keypath components. + /// + internal class AddCreateFoldersCommand + { + internal AddCreateFoldersCommand(IntermediateSection section) + { + this.Section = section; + } + + private IntermediateSection Section { get; } + + public void Execute() + { + var createFolderSymbolsByComponentRef = new HashSet(this.Section.Symbols.OfType().Select(t => t.ComponentRef)); + foreach (var componentSymbol in this.Section.Symbols.OfType().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList()) + { + if (!createFolderSymbolsByComponentRef.Contains(componentSymbol.Id.Id)) + { + this.Section.AddSymbol(new CreateFolderSymbol(componentSymbol.SourceLineNumbers) + { + DirectoryRef = componentSymbol.DirectoryRef, + ComponentRef = componentSymbol.Id.Id, + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs new file mode 100644 index 00000000..ee3bcc91 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs @@ -0,0 +1,95 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Add referenced standard directory symbols, if not already present. + /// + internal class AddRequiredStandardDirectories + { + internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform) + { + this.Section = section; + this.Platform = platform; + } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + public void Execute() + { + var directories = this.Section.Symbols.OfType().ToList(); + var directoryIds = new SortedSet(directories.Select(d => d.Id.Id)); + + foreach (var directory in directories) + { + var parentDirectoryId = directory.ParentDirectoryRef; + + if (String.IsNullOrEmpty(parentDirectoryId)) + { + if (directory.Id.Id != "TARGETDIR") + { + directory.ParentDirectoryRef = "TARGETDIR"; + } + } + else + { + this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers); + } + } + + if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) + { + directoryIds.Add(targetDir.Id.Id); + this.Section.AddSymbol(targetDir); + } + } + + private void EnsureStandardDirectoryAdded(ISet directoryIds, string directoryId, SourceLineNumber sourceLineNumbers) + { + if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) + { + var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); + + var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id) + { + Name = standardDirectory.Name, + ParentDirectoryRef = parentDirectoryId, + }; + + directoryIds.Add(directory.Id.Id); + this.Section.AddSymbol(directory); + + if (!String.IsNullOrEmpty(parentDirectoryId)) + { + this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers); + } + } + } + + private string GetStandardDirectoryParent(string directoryId) + { + switch (directoryId) + { + case "TARGETDIR": + return null; + + case "CommonFiles6432Folder": + case "ProgramFiles6432Folder": + case "System6432Folder": + return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform); + + default: + return "TARGETDIR"; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs new file mode 100644 index 00000000..759ba303 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs @@ -0,0 +1,60 @@ +// 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.Text; + + internal class AssemblyName + { + public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type) + { + this.Name = name; + this.Culture = culture ?? "neutral"; + this.Version = version; + this.FileVersion = fileVersion; + this.Architecture = architecture; + + this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); + this.PublicKeyToken = publicKeyToken; + this.Type = type; + } + + public string Name { get; } + + public string Culture { get; } + + public string Version { get; } + + public string FileVersion { get; } + + public string Architecture { get; } + + public string PublicKeyToken { get; } + + public bool StrongNamedSigned { get; } + + public string Type { get; } + + public string GetFullName() + { + var assemblyName = new StringBuilder(); + + assemblyName.Append(this.Name); + assemblyName.Append(", Version="); + assemblyName.Append(this.Version); + assemblyName.Append(", Culture="); + assemblyName.Append(this.Culture); + assemblyName.Append(", PublicKeyToken="); + assemblyName.Append(this.PublicKeyToken ?? "null"); + + if (!String.IsNullOrEmpty(this.Architecture)) + { + assemblyName.Append(", ProcessorArchitecture="); + assemblyName.Append(this.Architecture); + } + + return assemblyName.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs new file mode 100644 index 00000000..2103cd32 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs @@ -0,0 +1,214 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.IO; + using System.Reflection.Metadata; + using System.Reflection.PortableExecutable; + using System.Security.Cryptography; + using System.Text; + using System.Xml; + using System.Xml.XPath; + using WixToolset.Data; + + internal static class AssemblyNameReader + { + public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion) + { + try + { + using (var stream = File.OpenRead(assemblyPath)) + using (var peReader = new PEReader(stream)) + { + var reader = peReader.GetMetadataReader(); + var headers = peReader.PEHeaders; + + var assembly = reader.GetAssemblyDefinition(); + var attributes = assembly.GetCustomAttributes(); + + var name = ReadString(reader, assembly.Name); + var culture = ReadString(reader, assembly.Culture); + var architecture = ArchitectureFromHeaders(headers); + var version = assembly.Version.ToString(); + var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey); + + // 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. + var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0); + if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length) + { + var versionParts = version.Split('.'); + + if (versionParts.Length > 0) + { + var padding = new string('0', fileVersion.Length - version.Length); + + versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]); + version = String.Join(".", versionParts); + } + } + + return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null); + } + } + catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException) + { + throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}")); + } + } + + public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath) + { + 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 + { + var doc = new XPathDocument(manifestPath); + var 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()) + { + var hasNextSibling = true; + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) + { + hasNextSibling = nav.MoveToNext(); + } + + if (!hasNextSibling) + { + throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath)); + } + + 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) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest")); + } + catch (XmlException xe) + { + throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message)); + } + + return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type); + } + + private static string ArchitectureFromHeaders(PEHeaders headers) + { + if (headers.PEHeader.Magic == PEMagic.PE32Plus) + { + return "AMD64"; + } + else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit) + { + return "x86"; + } + else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly) + { + return "MSIL"; + } + else + { + // We return "x86" here because that seems to best match the Fusion-based + // GetAssemblyIdentityFromFile() method of acquiring the assembly identity. + return "x86"; + } + } + + private static string ReadString(MetadataReader reader, StringHandle handle) + { + return handle.IsNil ? null : reader.GetString(handle); + } + + private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle) + { + if (handle.IsNil) + { + return null; + } + + var bytes = reader.GetBlobBytes(handle); + if (bytes.Length == 0) + { + return null; + } + + var result = new StringBuilder(); + + // If we have the full public key, calculate the public key token from the + // last 8 bytes (in reverse order) of the public key's SHA1 hash. + if (bytes.Length > 8) + { + using (var sha1 = SHA1.Create()) + { + var hash = sha1.ComputeHash(bytes); + + for (var i = 1; i <= 8; ++i) + { + result.Append(hash[hash.Length - i].ToString("X2")); + } + } + } + else + { + foreach (var b in bytes) + { + result.Append(b.ToString("X2")); + } + } + + return result.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs new file mode 100644 index 00000000..cfa84629 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -0,0 +1,302 @@ +// 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.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. + /// + internal class AssignMediaCommand + { + private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB + + public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable fileFacades, bool compressed) + { + this.CabinetNameTemplate = "Cab{0}.cab"; + this.Section = section; + this.Messaging = messaging; + this.FileFacades = fileFacades; + this.FilesCompressed = compressed; + } + + private IntermediateSection Section { get; } + + private IMessaging Messaging { get; } + + private IEnumerable FileFacades { get; } + + private bool FilesCompressed { get; } + + private string CabinetNameTemplate { get; set; } + + /// + /// Gets cabinets with their file rows. + /// + public Dictionary> FileFacadesByCabinetMedia { 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() + { + var mediaSymbols = this.Section.Symbols.OfType().ToList(); + var mediaTemplateSymbols = this.Section.Symbols.OfType().ToList(); + + // If both symbols are authored, it is an error. + if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1) + { + throw new WixException(ErrorMessages.MediaTableCollision(null)); + } + + // If neither symbol is authored, default to a media template. + if (SectionType.Product == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0) + { + var mediaTemplate = new WixMediaTemplateSymbol() + { + CabinetTemplate = "cab{0}.cab", + }; + + this.Section.AddSymbol(mediaTemplate); + mediaTemplateSymbols.Add(mediaTemplate); + } + + // When building merge module, all the files go to "#MergeModule.CABinet". + if (SectionType.Module == this.Section.Type) + { + var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol + { + Cabinet = "#MergeModule.CABinet", + }); + + this.FileFacadesByCabinetMedia = new Dictionary> + { + { mergeModuleMediaSymbol, this.FileFacades } + }; + + this.UncompressedFileFacades = Array.Empty(); + } + else + { + var filesByCabinetMedia = new Dictionary>(); + var uncompressedFiles = new List(); + + if (mediaTemplateSymbols.Count > 0) + { + this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles); + } + else + { + this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles); + } + + this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value); + + this.UncompressedFileFacades = uncompressedFiles; + } + } + + /// + /// Assign files to cabinets based on MediaTemplate authoring. + /// + private void AutoAssignFiles(List mediaTemplateTable, List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) + { + const int MaxCabIndex = 999; + + ulong currentPreCabSize = 0; + ulong maxPreCabSizeInBytes; + var maxPreCabSizeInMB = 0; + var currentCabIndex = 0; + + MediaSymbol currentMediaRow = null; + + // Remove all previous media symbols since they will be replaced with + // media template. + foreach (var mediaSymbol in mediaSymbols) + { + this.Section.RemoveSymbol(mediaSymbol); + } + + // Auto assign files to cabinets based on maximum uncompressed media size + var mediaTemplateRow = mediaTemplateTable.Single(); + + if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) + { + this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; + } + + var 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 ?? DefaultMaximumUncompressedMediaSize; + } + + maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); + } + + var mediaSymbolsByDiskId = new Dictionary(); + + foreach (var 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 (SectionType.Product == this.Section.Type && (facade.Uncompressed || !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. + } + else + { + // Update current cab size. + currentPreCabSize += (ulong)facade.FileSize; + + // Overflow due to current file + if (currentPreCabSize > maxPreCabSizeInBytes) + { + currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); + mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + + // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize + currentPreCabSize = (ulong)facade.FileSize; + } + else // file fits in the current cab. + { + if (currentMediaRow == null) + { + // Create new cab and MediaRow + currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); + mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + } + } + } + + // Associate current file with current cab. + var cabinetFiles = filesByCabinetMedia[currentMediaRow]; + facade.DiskId = currentCabIndex; + cabinetFiles.Add(facade); + } + + // If there are uncompressed files and no MediaRow, create a default one. + if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0) + { + var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1)) + { + DiskId = 1, + }); + + mediaSymbolsByDiskId.Add(1, defaultMediaRow); + } + } + + /// + /// Assign files to cabinets based on Media authoring. + /// + private void ManuallyAssignFiles(List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) + { + var mediaSymbolsByDiskId = new Dictionary(); + + if (mediaSymbols.Any()) + { + var cabinetMediaSymbols = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var mediaSymbol in mediaSymbols) + { + // If the Media row has a cabinet, make sure it is unique across all Media rows. + if (!String.IsNullOrEmpty(mediaSymbol.Cabinet)) + { + if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow)) + { + this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet)); + this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); + } + else + { + cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol); + } + + filesByCabinetMedia.Add(mediaSymbol, new List()); + } + + mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol); + } + } + + foreach (var facade in this.FileFacades) + { + if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol)) + { + this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); + continue; + } + + // When building a product, if the current file is to be uncompressed or if + // the package set not to be compressed, don't cab it. + var compressed = facade.Compressed; + var uncompressed = facade.Uncompressed; + if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed))) + { + uncompressedFiles.Add(facade); + } + else // file is marked compressed. + { + if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles)) + { + cabinetFiles.Add(facade); + } + else + { + this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId)); + } + } + } + } + + /// + /// Adds a symbol to the section with cab name template filled in. + /// + /// + /// + /// + private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex) + { + return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex)) + { + DiskId = cabIndex, + Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex), + CompressionLevel = mediaTemplateSymbol.CompressionLevel, + }); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs new file mode 100644 index 00000000..76bcd532 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs @@ -0,0 +1,1305 @@ +// 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.Globalization; + using System.Linq; + using System.Text.RegularExpressions; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Services; + + /// + /// Include transforms in a patch. + /// + internal class AttachPatchTransformsCommand + { + private static readonly string[] PatchUninstallBreakingTables = new[] + { + "AppId", + "BindImage", + "Class", + "Complus", + "CreateFolder", + "DuplicateFile", + "Environment", + "Extension", + "Font", + "IniFile", + "IsolatedComponent", + "LockPermissions", + "MIME", + "MoveFile", + "MsiLockPermissionsEx", + "MsiServiceConfig", + "MsiServiceConfigFailureActions", + "ODBCAttribute", + "ODBCDataSource", + "ODBCDriver", + "ODBCSourceAttribute", + "ODBCTranslator", + "ProgId", + "PublishComponent", + "RemoveIniFile", + "SelfReg", + "ServiceControl", + "ServiceInstall", + "TypeLib", + "Verb", + }; + + private readonly TableDefinitionCollection tableDefinitions; + + public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable transforms) + { + this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Intermediate = intermediate; + this.Transforms = transforms; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private Intermediate Intermediate { get; } + + private IEnumerable Transforms { get; } + + public IEnumerable SubStorages { get; private set; } + + public IEnumerable Execute() + { + var subStorages = new List(); + + if (this.Transforms == null || !this.Transforms.Any()) + { + this.Messaging.Write(ErrorMessages.PatchWithoutTransforms()); + return subStorages; + } + + var summaryInfo = this.ExtractPatchSummaryInfo(); + + var section = this.Intermediate.Sections.First(); + + var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); + + // Get the patch id from the WixPatchId symbol. + var patchSymbol = symbols.OfType().FirstOrDefault(); + + if (String.IsNullOrEmpty(patchSymbol.Id?.Id)) + { + this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); + return subStorages; + } + + if (String.IsNullOrEmpty(patchSymbol.ClientPatchId)) + { + this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); + return subStorages; + } + + // enumerate patch.Media to map diskId to Media row + var patchMediaByDiskId = symbols.OfType().ToDictionary(t => t.DiskId); + + if (patchMediaByDiskId.Count == 0) + { + this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp()); + return subStorages; + } + + // populate MSP summary information + var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol); + + // enumerate transforms + var productCodes = new SortedSet(); + var transformNames = new List(); + var validTransform = new List>(); + + var baselineSymbolsById = symbols.OfType().ToDictionary(t => t.Id.Id); + + foreach (var mainTransform in this.Transforms) + { + var baselineSymbol = baselineSymbolsById[mainTransform.Baseline]; + + var patchRefSymbols = symbols.OfType().ToList(); + if (patchRefSymbols.Count > 0) + { + if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols)) + { + // transform has none of the content authored into this patch + continue; + } + } + + // Validate the transform doesn't break any patch specific rules. + this.Validate(mainTransform); + + // ensure consistent File.Sequence within each Media + var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; + + // Ensure that files are sequenced after the last file in any transform. + var transformMediaTable = mainTransform.Transform.Tables["Media"]; + if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) + { + foreach (MediaRow transformMediaRow in transformMediaTable.Rows) + { + if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < transformMediaRow.LastSequence) + { + // The Binder will pre-increment the sequence. + mediaSymbol.LastSequence = transformMediaRow.LastSequence; + } + } + } + + // Use the Media/@DiskId if greater than the last sequence for backward compatibility. + if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < mediaSymbol.DiskId) + { + mediaSymbol.LastSequence = mediaSymbol.DiskId; + } + + // Ignore media table in the transform. + mainTransform.Transform.Tables.Remove("Media"); + mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); + + var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); + + productCode = productCode.ToUpperInvariant(); + productCodes.Add(productCode); + validTransform.Add(Tuple.Create(productCode, mainTransform.Transform)); + + // attach these transforms to the patch object + // TODO: is this an acceptable way to auto-generate transform stream names? + var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture); + subStorages.Add(new SubStorage(transformName, mainTransform.Transform)); + subStorages.Add(new SubStorage("#" + transformName, pairedTransform)); + + transformNames.Add(":" + transformName); + transformNames.Add(":#" + transformName); + } + + if (validTransform.Count == 0) + { + this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms()); + return subStorages; + } + + // Validate that a patch authored as removable is actually removable + if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1") + { + var uninstallable = true; + + foreach (var entry in validTransform) + { + uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2); + } + + if (!uninstallable) + { + this.Messaging.Write(ErrorMessages.PatchNotRemovable()); + return subStorages; + } + } + + // Finish filling tables with transform-dependent data. + productCodes = FinalizePatchProductCodes(symbols, productCodes); + + // Semicolon delimited list of the product codes that can accept the patch. + summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) + { + PropertyId = SummaryInformationType.PatchProductCodes, + Value = String.Join(";", productCodes) + }); + + // Semicolon delimited list of transform substorage names in the order they are applied. + summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) + { + PropertyId = SummaryInformationType.TransformNames, + Value = String.Join(";", transformNames) + }); + + // Put the summary information that was extracted back in now that it is updated. + foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId)) + { + section.AddSymbol(readSummaryInfo); + } + + this.SubStorages = subStorages; + + return subStorages; + } + + private Dictionary ExtractPatchSummaryInfo() + { + var result = new Dictionary(); + + foreach (var section in this.Intermediate.Sections) + { + // Remove all summary information from the symbols and remember those that + // are not calculated or reserved. + foreach (var patchSummaryInfo in section.Symbols.OfType().ToList()) + { + section.RemoveSymbol(patchSummaryInfo); + + if (patchSummaryInfo.PropertyId != SummaryInformationType.PatchProductCodes && + patchSummaryInfo.PropertyId != SummaryInformationType.PatchCode && + patchSummaryInfo.PropertyId != SummaryInformationType.PatchInstallerRequirement && + patchSummaryInfo.PropertyId != SummaryInformationType.Reserved11 && + patchSummaryInfo.PropertyId != SummaryInformationType.Reserved14 && + patchSummaryInfo.PropertyId != SummaryInformationType.Reserved16) + { + result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo); + } + } + } + + return result; + } + + private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List symbols, WixPatchSymbol patchSymbol) + { + // PID_CODEPAGE + if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage)) + { + // Set the code page by default to the same code page for the + // string pool in the database. + AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers); + } + + // GUID patch code for the patch. + AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers); + + // Indicates the minimum Windows Installer version that is required to install the patch. + AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers); + + if (!summaryInfo.ContainsKey(SummaryInformationType.Security)) + { + AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced; + } + + // Use authored comments or default to display name. + MsiPatchMetadataSymbol commentsSymbol = null; + + var metadataSymbols = symbols.OfType().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property); + + if (!summaryInfo.ContainsKey(SummaryInformationType.Title) && + metadataSymbols.TryGetValue("DisplayName", out var displayName)) + { + AddSummaryInformation(SummaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers); + + // Default comments to use display name as-is. + commentsSymbol = displayName; + } + + // TODO: This code below seems unnecessary given the codepage is set at the top of this method. + //if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage) && + // metadataValues.TryGetValue("CodePage", out var codepage)) + //{ + // AddSummaryInformation(SummaryInformationType.Codepage, codepage); + //} + + if (!summaryInfo.ContainsKey(SummaryInformationType.PatchPackageName) && + metadataSymbols.TryGetValue("Description", out var description)) + { + AddSummaryInformation(SummaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers); + } + + if (!summaryInfo.ContainsKey(SummaryInformationType.Author) && + metadataSymbols.TryGetValue("ManufacturerName", out var manufacturer)) + { + AddSummaryInformation(SummaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers); + } + + // Special metadata marshalled through the build. + //var wixMetadataValues = symbols.OfType().ToDictionary(t => t.Id.Id, t => t.Value); + + //if (wixMetadataValues.TryGetValue("Comments", out var wixComments)) + if (metadataSymbols.TryGetValue("Comments", out var wixComments)) + { + commentsSymbol = wixComments; + } + + // Write the package comments to summary info. + if (!summaryInfo.ContainsKey(SummaryInformationType.Comments) && + commentsSymbol != null) + { + AddSummaryInformation(SummaryInformationType.Comments, commentsSymbol.Value, commentsSymbol.SourceLineNumbers); + } + + return metadataSymbols; + + void AddSummaryInformation(SummaryInformationType type, string value, SourceLineNumber sourceLineNumber) + { + summaryInfo.Add(type, new SummaryInformationSymbol(sourceLineNumber) + { + PropertyId = type, + Value = value + }); + } + } + + /// + /// Ensure transform is uninstallable. + /// + /// Product code in transform. + /// Transform generated by torch. + /// True if the transform is uninstallable + private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform) + { + var success = true; + + foreach (var tableName in PatchUninstallBreakingTables) + { + if (transform.TryGetTable(tableName, out var table)) + { + foreach (var row in table.Rows) + { + if (row.Operation == RowOperation.Add) + { + success = false; + + var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; + + this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); + } + } + } + } + + return success; + } + + /// + /// Reduce the transform according to the patch references. + /// + /// transform generated by torch. + /// Table contains patch family filter. + /// true if the transform is not empty + private bool ReduceTransform(WindowsInstallerData transform, IEnumerable patchRefSymbols) + { + // identify sections to keep + var oldSections = new Dictionary(); + var newSections = new Dictionary(); + var tableKeyRows = new Dictionary>(); + var sequenceList = new List
(); + var componentFeatureAddsIndex = new Dictionary>(); + var customActionTable = new Dictionary(); + var directoryTableAdds = new Dictionary(); + var featureTableAdds = new Dictionary(); + var keptComponents = new Dictionary(); + var keptDirectories = new Dictionary(); + var keptFeatures = new Dictionary(); + var keptLockPermissions = new HashSet(); + var keptMsiLockPermissionExs = new HashSet(); + + var componentCreateFolderIndex = new Dictionary>(); + var directoryLockPermissionsIndex = new Dictionary>(); + var directoryMsiLockPermissionsExIndex = new Dictionary>(); + + foreach (var patchRefSymbol in patchRefSymbols) + { + var tableName = patchRefSymbol.Table; + var key = patchRefSymbol.PrimaryKeys; + + // Short circuit filtering if all changes should be included. + if ("*" == tableName && "*" == key) + { + RemoveProductCodeFromTransform(transform); + return true; + } + + if (!transform.Tables.TryGetTable(tableName, out var table)) + { + // Table not found. + continue; + } + + // Index the table. + if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) + { + keyRows = new Dictionary(); + tableKeyRows.Add(tableName, keyRows); + + foreach (var newRow in table.Rows) + { + var primaryKey = newRow.GetPrimaryKey(); + keyRows.Add(primaryKey, newRow); + } + } + + if (!keyRows.TryGetValue(key, out var row)) + { + // Row not found. + continue; + } + + // Differ.sectionDelimiter + var sections = row.SectionId.Split('/'); + oldSections[sections[0]] = row; + newSections[sections[1]] = row; + } + + // throw away sections not referenced + var keptRows = 0; + Table directoryTable = null; + Table featureTable = null; + Table lockPermissionsTable = null; + Table msiLockPermissionsTable = null; + + foreach (var 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 (var i = 0; i < table.Rows.Count; i++) + { + var row = table.Rows[i]; + + if (table.Name == "CreateFolder") + { + var createFolderComponentId = row.FieldAsString(1); + + if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList)) + { + directoryList = new List(); + componentCreateFolderIndex.Add(createFolderComponentId, directoryList); + } + + directoryList.Add(row.FieldAsString(0)); + } + + if (table.Name == "CustomAction") + { + customActionTable.Add(row.FieldAsString(0), row); + } + + if (table.Name == "Directory") + { + directoryTable = table; + if (RowOperation.Add == row.Operation) + { + directoryTableAdds.Add(row.FieldAsString(0), row); + } + } + + if (table.Name == "Feature") + { + featureTable = table; + if (RowOperation.Add == row.Operation) + { + featureTableAdds.Add(row.FieldAsString(0), row); + } + } + + if (table.Name == "FeatureComponents") + { + if (RowOperation.Add == row.Operation) + { + var featureId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList)) + { + featureList = new List(); + componentFeatureAddsIndex.Add(componentId, featureList); + } + + featureList.Add(featureId); + } + } + + if (table.Name == "LockPermissions") + { + lockPermissionsTable = table; + if ("CreateFolder" == row.FieldAsString(1)) + { + var directoryId = row.FieldAsString(0); + + if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList)) + { + rowList = new List(); + directoryLockPermissionsIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (table.Name == "MsiLockPermissionsEx") + { + msiLockPermissionsTable = table; + if ("CreateFolder" == row.FieldAsString(1)) + { + var directoryId = row.FieldAsString(0); + + if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList)) + { + rowList = new List(); + directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (null == row.SectionId) + { + table.Rows.RemoveAt(i); + i--; + } + else + { + var 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(row.FieldAsString(0), row); + } + + if ("Directory" == table.Name) + { + keptDirectories.Add(row.FieldAsString(0), row); + } + + if ("Feature" == table.Name) + { + keptFeatures.Add(row.FieldAsString(0), row); + } + + keptRows++; + } + else + { + table.Rows.RemoveAt(i); + i--; + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + if (null != directoryTable) + { + foreach (var componentRow in keptComponents.Values) + { + var componentId = componentRow.FieldAsString(0); + + if (RowOperation.Add == componentRow.Operation) + { + // Make sure each added component has its required directory and feature heirarchy. + var directoryId = componentRow.FieldAsString(2); + while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow)) + { + if (!keptDirectories.ContainsKey(directoryId)) + { + directoryTable.Rows.Add(directoryRow); + keptDirectories.Add(directoryId, directoryRow); + keptRows++; + } + + directoryId = directoryRow.FieldAsString(1); + } + + if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds)) + { + foreach (var featureId in componentFeatureIds) + { + var currentFeatureId = featureId; + while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow)) + { + if (!keptFeatures.ContainsKey(currentFeatureId)) + { + featureTable.Rows.Add(featureRow); + keptFeatures.Add(currentFeatureId, featureRow); + keptRows++; + } + + currentFeatureId = featureRow.FieldAsString(1); + } + } + } + } + + // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. + foreach (var keptComponentId in keptComponents.Keys) + { + if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList)) + { + foreach (var directoryId in directoryList) + { + if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList)) + { + foreach (var lockPermissionsRow in lockPermissionsRowList) + { + var key = lockPermissionsRow.GetPrimaryKey('/'); + if (keptLockPermissions.Add(key)) + { + lockPermissionsTable.Rows.Add(lockPermissionsRow); + keptRows++; + } + } + } + + if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList)) + { + foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList) + { + var key = msiLockPermissionsExRow.GetPrimaryKey('/'); + if (keptMsiLockPermissionExs.Add(key)) + { + msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); + keptRows++; + } + } + } + } + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + // Delete tables that are empty. + var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList(); + + foreach (var tableName in tablesToDelete) + { + transform.Tables.Remove(tableName); + } + + return keptRows > 0; + } + + private void Validate(PatchTransform patchTransform) + { + var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? + var transform = patchTransform.Transform; + + // Changing the ProdocutCode in a patch transform is not recommended. + if (transform.TryGetTable("Property", out var propertyTable)) + { + foreach (var row in propertyTable.Rows) + { + // Only interested in modified rows; fast check. + if (RowOperation.Modify == row.Operation && + "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended()); + } + } + } + + // If there is nothing in the component table we can return early because the remaining checks are component based. + if (!transform.TryGetTable("Component", out var componentTable)) + { + return; + } + + // Index Feature table row operations + var featureOps = new Dictionary(); + if (transform.TryGetTable("Feature", out var featureTable)) + { + foreach (var row in featureTable.Rows) + { + featureOps[row.FieldAsString(0)] = row.Operation; + } + } + + // Index Component table and check for keypath modifications + var componentKeyPath = new Dictionary(); + var deletedComponent = new Dictionary(); + foreach (var row in componentTable.Rows) + { + var id = row.FieldAsString(0); + var keypath = row.FieldAsString(5) ?? String.Empty; + + componentKeyPath.Add(id, keypath); + + if (RowOperation.Delete == row.Operation) + { + deletedComponent.Add(id, row); + } + else if (RowOperation.Modify == row.Operation) + { + if (row.Fields[1].Modified) + { + // Changing the guid of a component is equal to deleting the old one and adding a new one. + deletedComponent.Add(id, row); + } + + // If the keypath is modified its an error + if (row.Fields[5].Modified) + { + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath)); + } + } + } + + // Verify changes in the file table + if (transform.TryGetTable("File", out var fileTable)) + { + var componentWithChangedKeyPath = new Dictionary(); + foreach (FileRow row in fileTable.Rows) + { + if (RowOperation.None == row.Operation) + { + continue; + } + + var fileId = row.File; + var componentId = row.Component; + + // If this file is the keypath of a component + if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) + { + if (row.Fields[2].Modified) + { + // You can't change the filename of a file that is the keypath of a component. + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath)); + } + + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + + if (RowOperation.Delete == row.Operation) + { + // If the file is removed from a component that is not deleted. + if (!deletedComponent.ContainsKey(componentId)) + { + var foundRemoveFileEntry = false; + var filename = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), false, true); + + if (transform.TryGetTable("RemoveFile", out var removeFileTable)) + { + foreach (var removeFileRow in removeFileTable.Rows) + { + if (RowOperation.Delete == removeFileRow.Operation) + { + continue; + } + + if (componentId == removeFileRow.FieldAsString(1)) + { + // Check if there is a RemoveFile entry for this file + if (null != removeFileRow[2]) + { + var removeFileName = this.BackendHelper.GetMsiFileName(removeFileRow.FieldAsString(2), false, true); + + // Convert the MSI format for a wildcard string to Regex format. + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); + + var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + if (regex.IsMatch(filename)) + { + foundRemoveFileEntry = true; + break; + } + } + } + } + } + + if (!foundRemoveFileEntry) + { + this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); + } + } + } + } + } + + var featureComponentsTable = transform.Tables["FeatureComponents"]; + + if (0 < deletedComponent.Count) + { + // Index FeatureComponents table. + var featureComponents = new Dictionary>(); + + if (null != featureComponentsTable) + { + foreach (var row in featureComponentsTable.Rows) + { + var componentId = row.FieldAsString(1); + + if (!featureComponents.TryGetValue(componentId, out var features)) + { + features = new List(); + featureComponents.Add(componentId, features); + } + + features.Add(row.FieldAsString(0)); + } + } + + // Check to make sure if a component was deleted, the feature was too. + foreach (var entry in deletedComponent) + { + if (featureComponents.TryGetValue(entry.Key, out var features)) + { + foreach (var featureId in features) + { + if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete) + { + // The feature was not deleted. + this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath)); + } + } + } + } + } + + // Warn if new components are added to existing features + if (null != featureComponentsTable) + { + foreach (var row in featureComponentsTable.Rows) + { + if (RowOperation.Add == row.Operation) + { + // Check if the feature is in the Feature table + var feature_ = row.FieldAsString(0); + var component_ = row.FieldAsString(1); + + // Features may not be present if not referenced + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) + { + this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath)); + } + } + } + } + } + + /// + /// Remove the ProductCode property from the transform. + /// + /// The transform. + /// + /// Changing the ProductCode is not supported in a patch. + /// + private static void RemoveProductCodeFromTransform(WindowsInstallerData transform) + { + if (transform.Tables.TryGetTable("Property", out var propertyTable)) + { + for (var i = 0; i < propertyTable.Rows.Count; ++i) + { + var propertyRow = propertyTable.Rows[i]; + var 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 + /// Dictionary contains section id should be kept in the baseline wixout. + /// Dictionary contains section id should be kept in the upgrade wixout. + /// true if section in patch family + private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary oldSections, Dictionary newSections) + { + var result = false; + + if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection))) + { + result = true; + } + else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(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(List
sequenceList, Dictionary oldSections, Dictionary newSections, Dictionary customAction) + { + var keptRows = 0; + + foreach (var currentTable in sequenceList) + { + for (var i = 0; i < currentTable.Rows.Count; i++) + { + var row = currentTable.Rows[i]; + var actionName = row.Fields[0].Data.ToString(); + var sections = row.SectionId.Split('/'); + var 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) + { + var sequenceChanged = row.Fields[2].Modified; + var 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. + /// + private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) + { + productCode = null; + + var pairedTransform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = mainTransform.Codepage + }; + + // lookup productVersion property to correct summaryInformation + var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1); + + var mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; + var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0)); + + var baselineValidationFlags = ((int)baselineSymbol.ValidationFlags).ToString(CultureInfo.InvariantCulture); + + if (!mainSummaryRows.ContainsKey((int)SummaryInformationType.TransformValidationFlags)) + { + var mainSummaryRow = mainSummaryTable.CreateRow(baselineSymbol.SourceLineNumbers); + mainSummaryRow[0] = (int)SummaryInformationType.TransformValidationFlags; + mainSummaryRow[1] = baselineValidationFlags; + } + + // copy summary information from core transform + var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); + + foreach (var mainSummaryRow in mainSummaryTable.Rows) + { + var type = (SummaryInformationType)mainSummaryRow.FieldAsInteger(0); + var value = mainSummaryRow.FieldAsString(1); + switch (type) + { + case SummaryInformationType.TransformProductCodes: + var propertyData = value.Split(';'); + var oldProductVersion = propertyData[0].Substring(38); + var 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 SummaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML. + value = baselineValidationFlags; + mainSummaryRow[1] = value; + break; + } + + var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers); + pairedSummaryRow[0] = mainSummaryRow[0]; + pairedSummaryRow[1] = value; + } + + if (productCode == null) + { + this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo()); + return null; + } + + // Copy File table + if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) + { + var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); + + foreach (FileRow mainFileRow in mainFileTable.Rows) + { + // 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; + } + + var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers); + pairedFileRow.Operation = RowOperation.Modify; + mainFileRow.CopyTo(pairedFileRow); + + // Override authored media for patch bind. + mainFileRow.DiskId = mediaSymbol.DiskId; + + // Suppress any change to File.Sequence to avoid bloat. + mainFileRow.Fields[7].Modified = false; + + // Force File row to appear in the transform. + switch (mainFileRow.Operation) + { + case RowOperation.Modify: + case RowOperation.Add: + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = mainFileRow.Operation; + break; + default: + pairedFileRow.Fields[6].Modified = false; + break; + } + } + } + + // Add Media row to pairedTransform + var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); + var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaSymbol.SourceLineNumbers); + pairedMediaRow.Operation = RowOperation.Add; + pairedMediaRow.DiskId = mediaSymbol.DiskId; + pairedMediaRow.LastSequence = mediaSymbol.LastSequence ?? 0; + pairedMediaRow.DiskPrompt = mediaSymbol.DiskPrompt; + pairedMediaRow.Cabinet = mediaSymbol.Cabinet; + pairedMediaRow.VolumeLabel = mediaSymbol.VolumeLabel; + pairedMediaRow.Source = mediaSymbol.Source; + + // Add PatchPackage for this Media + var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); + pairedPackageTable.Operation = TableOperation.Add; + var pairedPackageRow = pairedPackageTable.CreateRow(mediaSymbol.SourceLineNumbers); + pairedPackageRow.Operation = RowOperation.Add; + pairedPackageRow[0] = patchIdSymbol.Id.Id; + pairedPackageRow[1] = mediaSymbol.DiskId; + + // Add the property to the patch transform's Property table. + var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); + pairedPropertyTable.Operation = TableOperation.Add; + + // Add property to both identify client patches and whether those patches are removable or not + patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalSymbol); + + var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalSymbol?.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".AllowRemoval"); + pairedPropertyRow[1] = allowRemovalSymbol?.Value ?? "0"; + + // Add this patch code GUID to the patch transform to identify + // which patches are installed, including in multi-patch + // installations. + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".PatchCode"); + pairedPropertyRow[1] = patchIdSymbol.Id.Id; + + // Add PATCHNEWPACKAGECODE to apply to admin layouts. + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; + pairedPropertyRow[1] = patchIdSymbol.Id.Id; + + // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts. + if (summaryInfo.TryGetValue(SummaryInformationType.Subject, out var subjectSymbol)) + { + pairedPropertyRow = pairedPropertyTable.CreateRow(subjectSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; + pairedPropertyRow[1] = subjectSymbol.Value; + } + + if (summaryInfo.TryGetValue(SummaryInformationType.Comments, out var commentsSymbol)) + { + pairedPropertyRow = pairedPropertyTable.CreateRow(commentsSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; + pairedPropertyRow[1] = commentsSymbol.Value; + } + + return pairedTransform; + } + + private static SortedSet FinalizePatchProductCodes(List symbols, SortedSet productCodes) + { + var patchTargetSymbols = symbols.OfType().ToList(); + + if (patchTargetSymbols.Any()) + { + var targets = new SortedSet(); + var replace = true; + foreach (var wixPatchTargetRow in patchTargetSymbols) + { + var target = wixPatchTargetRow.ProductCode.ToUpperInvariant(); + if (target == "*") + { + replace = false; + } + else + { + targets.Add(target); + } + } + + // Replace the target ProductCodes with the authored list. + if (replace) + { + productCodes = targets; + } + else + { + // Copy the authored target ProductCodes into the list. + foreach (var target in targets) + { + productCodes.Add(target); + } + } + } + + return productCodes; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..9f36cd78 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -0,0 +1,646 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Binds a databse. + /// + internal class BindDatabaseCommand + { + // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. + internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); + + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) + { + } + + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) + { + this.ServiceProvider = context.ServiceProvider; + + this.Messaging = context.ServiceProvider.GetService(); + + this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); + + this.PathResolver = this.ServiceProvider.GetService(); + + this.CabbingThreadCount = context.CabbingThreadCount; + this.CabCachePath = context.CabCachePath; + this.DefaultCompressionLevel = context.DefaultCompressionLevel; + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); + this.Intermediate = context.IntermediateRepresentation; + this.IntermediateFolder = context.IntermediateFolder; + this.OutputPath = context.OutputPath; + this.OutputPdbPath = context.PdbPath; + this.PdbType = context.PdbType; + this.ResolvedCodepage = context.ResolvedCodepage; + this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; + this.ResolvedLcid = context.ResolvedLcid; + this.SuppressLayout = context.SuppressLayout; + + this.SubStorages = subStorages; + + this.SuppressValidation = context.SuppressValidation; + this.Ices = context.Ices; + this.SuppressedIces = context.SuppressIces; + this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; + + this.BackendExtensions = backendExtension; + } + + public IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private int CabbingThreadCount { get; } + + private string CabCachePath { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + public FileSystemManager FileSystemManager { get; } + + public bool DeltaBinaryPatch { get; set; } + + private IEnumerable BackendExtensions { get; } + + private IEnumerable SubStorages { get; } + + private Intermediate Intermediate { get; } + + private string OutputPath { get; } + + public PdbType PdbType { get; set; } + + private string OutputPdbPath { get; } + + private int? ResolvedCodepage { get; } + + private int? ResolvedSummaryInformationCodepage { get; } + + private int? ResolvedLcid { get; } + + private bool SuppressAddingValidationRows { get; } + + private bool SuppressLayout { get; } + + private string IntermediateFolder { get; } + + private bool SuppressValidation { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private IEnumerable CubeFiles { get; } + + public IBindResult Execute() + { + if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) + { + this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); + } + + var section = this.Intermediate.Sections.Single(); + + var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol(section) : null; + var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol(section) : null; + var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol(section) : null; + + var fileTransfers = new List(); + var trackedFiles = new List(); + + var containsMergeModules = false; + + // Load standard tables, authored custom tables, and extension custom tables. + TableDefinitionCollection tableDefinitions; + { + var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); + command.Execute(); + + tableDefinitions = command.TableDefinitions; + } + + // Calculate codepage + var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); + + // Process properties and create the delayed variable cache if needed. + Dictionary variableCache = null; + string productLanguage = null; + { + var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); + command.Execute(); + + variableCache = command.DelayedVariablesCache; + productLanguage = command.ProductLanguage; + } + + // Process the summary information table after properties are processed. + bool compressed; + bool longNames; + int installerVersion; + Platform platform; + string modularizationSuffix; + { + var branding = this.ServiceProvider.GetService(); + + var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); + command.Execute(); + + compressed = command.Compressed; + longNames = command.LongNames; + installerVersion = command.InstallerVersion; + platform = command.Platform; + modularizationSuffix = command.ModularizationSuffix; + } + + // Sequence all the actions. + { + var command = new SequenceActionsCommand(this.Messaging, section); + command.Execute(); + } + + if (section.Type == SectionType.Product || section.Type == SectionType.Module) + { + var command = new AddRequiredStandardDirectories(section, platform); + command.Execute(); + } + + { + var command = new CreateSpecialPropertiesCommand(section); + command.Execute(); + } + +#if TODO_PATCHING + ////if (OutputType.Patch == this.Output.Type) + ////{ + //// foreach (SubStorage substorage in this.Output.SubStorages) + //// { + //// Output transform = substorage.Data; + + //// ResolveFieldsCommand command = new ResolveFieldsCommand(); + //// command.Tables = transform.Tables; + //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + //// command.FileManagerCore = this.FileManagerCore; + //// command.FileManagers = this.FileManagers; + //// command.SupportDelayedResolution = false; + //// command.TempFilesLocation = this.TempFilesLocation; + //// command.WixVariableResolver = this.WixVariableResolver; + //// command.Execute(); + + //// this.MergeUnrealTables(transform.Tables); + //// } + ////} +#endif + + if (this.Messaging.EncounteredError) + { + return null; + } + + this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); + this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); + + // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). + { + var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); + + trackedFiles.AddRange(extractedFiles); + } + + // This must occur after all variables and source paths have been resolved. + List fileFacades; + if (SectionType.Patch == section.Type) + { + var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); + command.Execute(); + + fileFacades = command.FileFacades; + } + else + { + var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); + command.Execute(); + + fileFacades = command.FileFacades; + } + + // Retrieve file information from merge modules. + if (SectionType.Product == section.Type) + { + var wixMergeSymbols = section.Symbols.OfType().ToList(); + + if (wixMergeSymbols.Any()) + { + containsMergeModules = true; + + var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); + command.Execute(); + + fileFacades.AddRange(command.MergeModulesFileFacades); + } + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // Process SoftwareTags in MSI packages. + if (SectionType.Product == section.Type) + { + var softwareTags = section.Symbols.OfType().ToList(); + + if (softwareTags.Any()) + { + var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); + command.Execute(); + } + } + + // Gather information about files that do not come from merge modules. + { + var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); + command.Execute(); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // Now that the variable cache is populated, resolve any delayed fields. + if (this.DelayedFields.Any()) + { + this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); + } + + // Update symbols that reference text files on disk. + { + var command = new UpdateFromTextFilesCommand(this.Messaging, section); + command.Execute(); + } + + // Add missing CreateFolder symbols to null-keypath components. + { + var command = new AddCreateFoldersCommand(section); + command.Execute(); + } + + // Process dependency references. + if (SectionType.Product == section.Type || SectionType.Module == section.Type) + { + var dependencyRefs = section.Symbols.OfType().ToList(); + + if (dependencyRefs.Any()) + { + var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); + command.Execute(); + } + } + + // If there are any backend extensions, give them the opportunity to process + // the section now that the fields have all be resolved. + // + if (this.BackendExtensions.Any()) + { + using (new IntermediateFieldContext("wix.bind.finalize")) + { + foreach (var extension in this.BackendExtensions) + { + extension.SymbolsFinalized(section); + } + + var reresolvedFiles = section.Symbols + .OfType() + .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize")) + .ToList(); + + if (reresolvedFiles.Any()) + { + var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); + + var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); + command.Execute(); + } + } + + if (this.Messaging.EncounteredError) + { + return null; + } + } + + // Set generated component guids and validate all guids. + { + var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); + command.Execute(); + } + + // Assign files to media and update file sequences. + Dictionary> filesByCabinetMedia; + IEnumerable uncompressedFiles; + { + var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades); + order.Execute(); + + fileFacades = order.FileFacades; + + var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); + assign.Execute(); + + filesByCabinetMedia = assign.FileFacadesByCabinetMedia; + uncompressedFiles = assign.UncompressedFileFacades; + + var update = new UpdateMediaSequencesCommand(section, fileFacades); + update.Execute(); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. + WindowsInstallerData data; + { + var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); + data = command.Execute(); + } + + IEnumerable suppressedTableNames = null; + if (data.Type == OutputType.Module) + { + // Modularize identifiers. + var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType()); + modularize.Execute(); + + // Ensure all sequence tables in place because, mergemod.dll requires them. + var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); + suppressedTableNames = unsuppress.Execute(); + } + else if (data.Type == OutputType.Patch) + { + foreach (var storage in this.SubStorages) + { + data.SubStorages.Add(storage); + } + } + + // Stop processing if an error previously occurred. + if (this.Messaging.EncounteredError) + { + return null; + } + + // Ensure the intermediate folder is created since delta patches will be + // created there. + Directory.CreateDirectory(this.IntermediateFolder); + + if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) + { + var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); + command.Execute(); + } + + // create cabinet files and process uncompressed files + var layoutDirectory = Path.GetDirectoryName(this.OutputPath); + if (!this.SuppressLayout || OutputType.Module == data.Type) + { + this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); + + var mediaTemplate = section.Symbols.OfType().FirstOrDefault(); + + var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate); + command.CabbingThreadCount = this.CabbingThreadCount; + command.CabCachePath = this.CabCachePath; + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Data = data; + command.Messaging = this.Messaging; + command.BackendExtensions = this.BackendExtensions; + command.LayoutDirectory = layoutDirectory; + command.Compressed = compressed; + command.ModularizationSuffix = modularizationSuffix; + command.FileFacadesByCabinet = filesByCabinetMedia; + command.ResolveMedia = this.ResolveMedia; + command.TableDefinitions = tableDefinitions; + command.IntermediateFolder = this.IntermediateFolder; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // We can create instance transforms since Component Guids and Outputs are created. + if (data.Type == OutputType.Product) + { + var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); + command.Execute(); + } + else if (data.Type == OutputType.Patch) + { + // Copy output data back into the transforms. + var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades); + command.Execute(); + } + + // Generate database file. + { + this.Messaging.Write(VerboseMessages.GeneratingDatabase()); + + var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); + trackedFiles.Add(trackMsi); + + var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); + command.Execute(); + + trackedFiles.AddRange(command.GeneratedTemporaryFiles); + } + + // Stop processing if an error previously occurred. + if (this.Messaging.EncounteredError) + { + return null; + } + + // Merge modules. + if (containsMergeModules) + { + this.Messaging.Write(VerboseMessages.MergingModules()); + + var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Validate the output if there are CUBe files and we're not explicitly suppressing validation. + if (this.CubeFiles != null && !this.SuppressValidation) + { + var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); + command.Execute(); + + trackedFiles.AddRange(command.TrackedFiles); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Process uncompressed files. + if (!this.SuppressLayout && uncompressedFiles.Any()) + { + var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); + command.Compressed = compressed; + command.FileFacades = uncompressedFiles; + command.LayoutDirectory = layoutDirectory; + command.LongNamesInImage = longNames; + command.ResolveMedia = this.ResolveMedia; + command.DatabasePath = this.OutputPath; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + } + + // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). + trackedFiles.AddRange(fileFacades.Select(f => this.WindowsInstallerBackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); + + var result = this.ServiceProvider.GetService(); + result.FileTransfers = fileTransfers; + result.TrackedFiles = trackedFiles; + result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data); + + return result; + } + + private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) + { + var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; + + if (String.IsNullOrEmpty(codepage)) + { + codepage = this.ResolvedCodepage?.ToString() ?? "65001"; + + if (packageSymbol != null) + { + packageSymbol.Codepage = codepage; + } + else if (moduleSymbol != null) + { + moduleSymbol.Codepage = codepage; + } + else if (patchSymbol != null) + { + patchSymbol.Codepage = codepage; + } + } + + return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); + } + + private T GetSingleSymbol(IntermediateSection section) where T : IntermediateSymbol + { + var symbols = section.Symbols.OfType().ToList(); + + if (1 != symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols[0]; + } + + private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData data) + { + WixOutput wixout; + + if (String.IsNullOrEmpty(this.OutputPdbPath)) + { + wixout = WixOutput.Create(); + } + else + { + var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); + trackedFiles.Add(trackPdb); + + wixout = WixOutput.Create(trackPdb.Path); + } + + intermediate.Save(wixout); + + data.Save(wixout); + + wixout.Reopen(); + + return wixout; + } + + private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) + { + string layout = null; + + foreach (var extension in this.BackendExtensions) + { + layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); + if (!String.IsNullOrEmpty(layout)) + { + break; + } + } + + // If no binder file manager resolved the layout, do the default behavior. + if (String.IsNullOrEmpty(layout)) + { + if (String.IsNullOrEmpty(mediaLayoutDirectory)) + { + layout = layoutDirectory; + } + else if (Path.IsPathRooted(mediaLayoutDirectory)) + { + layout = mediaLayoutDirectory; + } + else + { + layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); + } + } + + return layout; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..41da2a13 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs @@ -0,0 +1,211 @@ +// 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.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + /// + /// Binds the summary information table of a database. + /// + internal class BindSummaryInfoCommand + { + public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding) + { + this.Section = section; + this.SummaryInformationCodepage = summaryInformationCodepage; + this.ProductLanguage = productLanguage; + this.BackendHelper = backendHelper; + this.Branding = branding; + } + + private IntermediateSection Section { get; } + + private int? SummaryInformationCodepage { get; } + + private string ProductLanguage { get; } + + private IBackendHelper BackendHelper { get; } + + private IWixBranding Branding { get; } + + /// + /// 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; } + + public Platform Platform { get; private set; } + + /// + /// Modularization guid, or null if the output is not a module. + /// + public string ModularizationSuffix { get; private set; } + + public void Execute() + { + this.Compressed = false; + this.LongNames = false; + this.InstallerVersion = 0; + this.ModularizationSuffix = null; + + SummaryInformationSymbol summaryInformationCodepageSymbol = null; + SummaryInformationSymbol platformAndLanguageSymbol = null; + var foundCreateDateTime = false; + var foundLastSaveDataTime = false; + var foundCreatingApplication = false; + var foundPackageCode = false; + var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); + + foreach (var summaryInformationSymbol in this.Section.Symbols.OfType()) + { + switch (summaryInformationSymbol.PropertyId) + { + case SummaryInformationType.Codepage: // PID_CODEPAGE + summaryInformationCodepageSymbol = summaryInformationSymbol; + break; + + case SummaryInformationType.PlatformAndLanguage: + platformAndLanguageSymbol = summaryInformationSymbol; + break; + + case SummaryInformationType.PackageCode: // PID_REVNUMBER + foundPackageCode = true; + var packageCode = summaryInformationSymbol.Value; + + if (SectionType.Module == this.Section.Type) + { + this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_'); + } + break; + case SummaryInformationType.Created: + foundCreateDateTime = true; + break; + case SummaryInformationType.LastSaved: + foundLastSaveDataTime = true; + break; + case SummaryInformationType.WindowsInstallerVersion: + this.InstallerVersion = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); + break; + case SummaryInformationType.WordCount: + if (SectionType.Patch == this.Section.Type) + { + this.LongNames = true; + this.Compressed = true; + } + else + { + var attributes = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); + this.LongNames = (0 == (attributes & 1)); + this.Compressed = (2 == (attributes & 2)); + } + break; + case SummaryInformationType.CreatingApplication: // PID_APPNAME + foundCreatingApplication = true; + break; + } + } + + // Ensure the codepage is set properly. + if (summaryInformationCodepageSymbol == null) + { + summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.Codepage + }); + } + + var codepage = summaryInformationCodepageSymbol.Value; + + if (String.IsNullOrEmpty(codepage)) + { + codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252"; + } + + summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture); + + // Ensure the language is set properly and figure out what platform we are targeting. + if (platformAndLanguageSymbol != null) + { + this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage); + } + + // Set the revision number (package/patch code) if it should be automatically generated. + if (!foundPackageCode) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.PackageCode, + Value = this.BackendHelper.CreateGuid(), + }); + } + + // add a summary information row for the create time/date property if its not already set + if (!foundCreateDateTime) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.Created, + Value = now, + }); + } + + // add a summary information row for the last save time/date property if its not already set + if (!foundLastSaveDataTime) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.LastSaved, + Value = now, + }); + } + + // add a summary information row for the creating application property if its not already set + if (!foundCreatingApplication) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.CreatingApplication, + Value = this.Branding.GetCreatingApplication(), + }); + } + } + + private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language) + { + var value = symbol.Value; + var separatorIndex = value.IndexOf(';'); + var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; + + // If the language was provided and there was language value after the separator + // (or the separator was absent) then use the provided language. + if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length)) + { + symbol.Value = platformValue + ';' + language; + } + + switch (platformValue) + { + case "x64": + return Platform.X64; + + case "Arm64": + return Platform.ARM64; + + case "Intel": + default: + return Platform.X86; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..3379ec5d --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -0,0 +1,445 @@ +// 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.Globalization; + using System.IO; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class BindTransformCommand + { + public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; + this.IntermediateFolder = intermediateFolder; + this.Transform = transform; + this.OutputPath = outputPath; + this.TableDefinitions = tableDefinitions; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private FileSystemManager FileSystemManager { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string IntermediateFolder { get; } + + private WindowsInstallerData Transform { get; } + + private string OutputPath { get; } + + public void Execute() + { + var transformFlags = 0; + + var targetOutput = new WindowsInstallerData(null); + var updatedOutput = new WindowsInstallerData(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; + + if (this.Transform.TryGetTable("Property", out var 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]; + } + } + } + } + + var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); + var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); + + // process special summary information values + foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) + { + var summaryId = row.FieldAsInteger(0); + var summaryData = row.FieldAsString(1); + + if ((int)SummaryInformation.Transform.CodePage == summaryId) + { + // convert from a web name if provided + var codePage = summaryData; + if (null == codePage) + { + codePage = "0"; + } + else + { + codePage = this.BackendHelper.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); + } + + var previousCodePage = row.Fields[1].PreviousData; + if (null == previousCodePage) + { + previousCodePage = "0"; + } + else + { + previousCodePage = this.BackendHelper.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); + } + + var targetCodePageRow = targetSummaryInfo.CreateRow(null); + targetCodePageRow[0] = 1; // PID_CODEPAGE + targetCodePageRow[1] = previousCodePage; + + var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); + updatedCodePageRow[0] = 1; // PID_CODEPAGE + updatedCodePageRow[1] = codePage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || + (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) + { + // the target language + var propertyData = summaryData.Split(';'); + var lang = 2 == propertyData.Length ? propertyData[1] : "0"; + + var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; + var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; + + var productLanguageRow = tempPropertyTable.CreateRow(null); + productLanguageRow[0] = "ProductLanguage"; + productLanguageRow[1] = lang; + + // set the platform;language on the MSI to be generated + var templateRow = tempSummaryInfo.CreateRow(null); + templateRow[0] = 7; // PID_TEMPLATE + templateRow[1] = summaryData; + } + else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) + { + var propertyData = summaryData.Split(';'); + + var targetProductCodeRow = targetPropertyTable.CreateRow(null); + targetProductCodeRow[0] = "ProductCode"; + targetProductCodeRow[1] = propertyData[0].Substring(0, 38); + + var targetProductVersionRow = targetPropertyTable.CreateRow(null); + targetProductVersionRow[0] = "ProductVersion"; + targetProductVersionRow[1] = propertyData[0].Substring(38); + + var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); + updatedProductCodeRow[0] = "ProductCode"; + updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); + + var 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)) + { + var 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)) + { + var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); + updatedUpgradeCodeRow[0] = "UpgradeCode"; + updatedUpgradeCodeRow[1] = updatedUpgradeCode; + } + } + else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) + { + transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) + { + // PID_LASTPRINTED should be null for transforms + row.Operation = RowOperation.None; + } + else + { + // add everything else as is + var targetRow = targetSummaryInfo.CreateRow(null); + targetRow[0] = row[0]; + targetRow[1] = row[1]; + + var 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))) + { + this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired()); + } + + string emptyFile = null; + + foreach (var 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 (var row in table.Rows) + { + switch (row.Operation) + { + case RowOperation.Add: + var updatedTable = updatedOutput.EnsureTable(table.Definition); + updatedTable.Rows.Add(row); + continue; + + case RowOperation.Delete: + var targetTable = targetOutput.EnsureTable(table.Definition); + targetTable.Rows.Add(row); + + // fill-in non-primary key values + foreach (var 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.IntermediateFolder, "empty"); + } + + field.Data = emptyFile; + } + else + { + field.Data = "0"; + } + } + } + continue; + } + + // Assure that the file table's sequence is populated + if ("File" == table.Name) + { + foreach (var fileRow in table.Rows) + { + if (null == fileRow[7]) + { + if (RowOperation.Add == fileRow.Operation) + { + this.Messaging.Write(ErrorMessages.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 + var modifiedRow = false; + var targetRow = table.Definition.CreateRow(null); + var updatedRow = row; + for (var i = 0; i < row.Fields.Length; i++) + { + var 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) + { + var data = updatedField.AsNullableInteger(); + targetRow[i] = (data == 1) ? 2 : 1; + } + else if (ColumnType.Object == updatedField.Column.Type) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); + } + + targetRow[i] = emptyFile; + } + else + { + var data = updatedField.AsString(); + targetRow[i] = (data == "0") ? "1" : "0"; + } + + modifiedRow = true; + } + else if (ColumnType.Object == updatedField.Column.Type) + { + var objectField = (ObjectField)updatedField; + + // create an empty file for comparing against + if (null == objectField.PreviousData) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); + } + + targetRow[i] = emptyFile; + modifiedRow = true; + } + else if (!this.FileSystemManager.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]))) + { + var targetTable = targetOutput.EnsureTable(table.Definition); + targetTable.Rows.Add(targetRow); + + var 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 (this.Messaging.EncounteredError) + { + return; + } + + var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); + var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); + var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); + + try + { + if (!String.IsNullOrEmpty(emptyFile)) + { + using (var fileStream = File.Create(emptyFile)) + { + } + } + + this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); + this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); + + // make sure the directory exists + Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); + + // create the transform file + using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) + using (var 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 + { + this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); + } + } + } + finally + { + if (!String.IsNullOrEmpty(emptyFile)) + { + File.Delete(emptyFile); + } + } + } + + private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) + { + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); + command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs new file mode 100644 index 00000000..13b079ad --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -0,0 +1,171 @@ +// 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 System.Threading; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// 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 readonly Queue cabinetWorkItems; + private int threadCount; + + // Address of Binder's callback function for Cabinet Splitting + private readonly IntPtr newCabNamesCallBackAddress; + + /// + /// Instantiate a new CabinetBuilder. + /// + /// + /// number of threads to use + /// Address of Binder's callback function for Cabinet Splitting + public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress) + { + if (0 >= threadCount) + { + throw new ArgumentOutOfRangeException(nameof(threadCount)); + } + + this.cabinetWorkItems = new Queue(); + this.Messaging = messaging; + this.threadCount = threadCount; + + // Set Address of Binder's callback function for Cabinet Splitting + this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; + } + + private IMessaging Messaging { get; } + + public int MaximumCabinetSizeForLargeFileSplitting { get; set; } + + public int MaximumUncompressedMediaSize { get; set; } + + /// + /// 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 + var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count); + + if (0 < numberOfThreads) + { + var threads = new Thread[numberOfThreads]; + + for (var i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); + threads[i].Start(); + } + + // wait for all threads to finish + foreach (var 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 = this.cabinetWorkItems.Dequeue(); + } + + // create a cabinet + this.CreateCabinet(cabinetWorkItem); + } + } + catch (WixException we) + { + this.Messaging.Write(we.Error); + } + catch (Exception e) + { + this.Messaging.Write(ErrorMessages.UnexpectedException(e)); + } + } + + /// + /// Creates a cabinet using the wixcab.dll interop layer. + /// + /// CabinetWorkItem containing information about the cabinet to create. + private void CreateCabinet(CabinetWorkItem cabinetWorkItem) + { + this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile)); + + var maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting + ulong maxPreCompressedSizeInBytes = 0; + + if (this.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)this.MaximumUncompressedMediaSize * 1024 * 1024; + + var facade = cabinetWorkItem.FileFacades.First(); + + // If the file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting + if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes) + { + maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting; + } + } + } + + // create the cabinet file + var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); + + var files = cabinetWorkItem.FileFacades + .OrderBy(f => f.Sequence) + .Select(facade => facade.Hash == null ? + new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) : + new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) + .ToList(); + + var cab = new Cabinet(cabinetPath); + cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); + + // TODO: Handle newCabNamesCallBackAddress from compression. + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs new file mode 100644 index 00000000..875b46c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs @@ -0,0 +1,132 @@ +// 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.Native; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CabinetResolver + { + public CabinetResolver(IServiceProvider serviceProvider, string cabCachePath, IEnumerable backendExtensions) + { + this.ServiceProvider = serviceProvider; + + this.CabCachePath = cabCachePath; + + this.BackendExtensions = backendExtensions; + } + + private IServiceProvider ServiceProvider { get; } + + private string CabCachePath { get; } + + private IEnumerable BackendExtensions { get; } + + public IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) + { + var filesWithPath = fileFacades.Select(this.CreateBindFileWithPath).ToList(); + + IResolvedCabinet 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 = this.ServiceProvider.GetService(); + resolved.BuildOption = CabinetBuildOption.BuildAndMove; + resolved.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)) + { + var 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 + var cabinetValid = true; + + var cabinet = new Cabinet(resolved.Path); + var fileList = cabinet.Enumerate(); + + if (filesWithPath.Count() != fileList.Count) + { + cabinetValid = false; + } + else + { + var i = 0; + foreach (var file in filesWithPath) + { + // First check that the file identifiers match because that is quick and easy. + var cabFileInfo = fileList[i]; + cabinetValid = (cabFileInfo.FileId == file.Id); + if (cabinetValid) + { + // Still valid so ensure the file sizes are the same. + var fileInfo = new FileInfo(file.Path); + cabinetValid = (cabFileInfo.Size == fileInfo.Length); + if (cabinetValid) + { + // Still valid so ensure the source time stamp hasn't changed. + cabinetValid = cabFileInfo.SameAsDateTime(fileInfo.LastWriteTime); + } + } + + if (!cabinetValid) + { + break; + } + + i++; + } + } + + resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; + } + } + + return resolved; + } + + private IBindFileWithPath CreateBindFileWithPath(IFileFacade facade) + { + var result = this.ServiceProvider.GetService(); + result.Id = facade.Id; + result.Path = facade.SourcePath; + + return result; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs new file mode 100644 index 00000000..1990ea78 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.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.Bind +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + /// + /// A cabinet builder work item. + /// + internal sealed class CabinetWorkItem + { + /// + /// 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. + /// Modularization suffix used when building a Merge Module. + /// + public CabinetWorkItem(IEnumerable fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix /*, BinderFileManager binderFileManager*/) + { + this.CabinetFile = cabinetFile; + this.CompressionLevel = compressionLevel; + this.ModularizationSuffix = modularizationSuffix; + this.FileFacades = fileFacades; + //this.BinderFileManager = binderFileManager; + this.MaxThreshold = maxThreshold; + } + + /// + /// Gets the cabinet file. + /// + /// The cabinet file. + public string CabinetFile { get; } + + /// + /// Gets the compression level of the cabinet. + /// + /// The compression level of the cabinet. + public CompressionLevel CompressionLevel { get; } + + /// + /// Gets the modularization suffix used when building a Merge Module. + /// + public string ModularizationSuffix { get; } + + /// + /// Gets the collection of files in this cabinet. + /// + /// The collection of files in this cabinet. + public IEnumerable FileFacades { get; } + + // + // Gets the binder file manager. + // + // The binder file manager. + //public BinderFileManager BinderFileManager { get; private set; } + + /// + /// Gets the max threshold. + /// + /// The maximum threshold for a folder in a cabinet. + public int MaxThreshold { get; } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs new file mode 100644 index 00000000..83a4949e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -0,0 +1,455 @@ +// 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.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// 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 readonly List fileTransfers; + + private readonly List trackedFiles; + + private readonly FileSplitCabNamesCallback newCabNamesCallBack; + + private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence + + public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate) + { + this.fileTransfers = new List(); + + this.trackedFiles = new List(); + + this.newCabNamesCallBack = this.NewCabNamesCallBack; + + this.ServiceProvider = serviceProvider; + + this.BackendHelper = backendHelper; + + this.MediaTemplate = mediaTemplate; + } + + private IServiceProvider ServiceProvider { get; } + + private IBackendHelper BackendHelper { get; } + + private WixMediaTemplateSymbol MediaTemplate { get; } + + /// + /// Sets the number of threads to use for cabinet creation. + /// + public int CabbingThreadCount { private get; set; } + + public string CabCachePath { private get; set; } + + public IMessaging Messaging { private get; set; } + + public string IntermediateFolder { 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 WindowsInstallerData Data { private get; set; } + + public string LayoutDirectory { private get; set; } + + public bool Compressed { private get; set; } + + public string ModularizationSuffix { private get; set; } + + public Dictionary> FileFacadesByCabinet { private get; set; } + + public Func ResolveMedia { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public IEnumerable FileTransfers => this.fileTransfers; + + public IEnumerable TrackedFiles => this.trackedFiles; + + public void Execute() + { + this.lastCabinetAddedToMediaTable = new Dictionary(); + + // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. + if (this.CabbingThreadCount <= 0) + { + this.CabbingThreadCount = this.CalculateCabbingThreadCount(); + + this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); + } + + // Send Binder object to Facilitate NewCabNamesCallBack Callback + var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); + + // Supply Compile MediaTemplate Attributes to Cabinet Builder + this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); + cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; + cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; + + foreach (var entry in this.FileFacadesByCabinet) + { + var mediaSymbol = entry.Key; + var files = entry.Value; + var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; + var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); + + var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); + if (null != cabinetWorkItem) + { + cabinetBuilder.Enqueue(cabinetWorkItem); + } + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return; + } + + // create queued cabinets with multiple threads + cabinetBuilder.CreateQueuedCabinets(); + if (this.Messaging.EncounteredError) + { + return; + } + } + + private int CalculateCabbingThreadCount() + { + var cabbingThreadCount = Environment.ProcessorCount; + + if (cabbingThreadCount <= 0) + { + cabbingThreadCount = 1; // reset to 1 when the environment variable is invalid. + + this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), cabbingThreadCount.ToString())); + } + + return cabbingThreadCount; + } + + /// + /// Creates a work item to create a cabinet. + /// + /// Windows Installer data for the current database. + /// Directory to create cabinet in. + /// Media symbol containing information about the cabinet. + /// Desired compression level. + /// Collection of files in this cabinet. + /// created CabinetWorkItem object + private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades) + { + CabinetWorkItem cabinetWorkItem = null; + var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); + + // check for an empty cabinet + if (!fileFacades.Any()) + { + // Remove the leading '#' from the embedded cabinet name to make the warning easier to understand + var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); + + // If building a patch, remind them to run -p for torch. + if (OutputType.Patch == data.Type) + { + this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true)); + } + else + { + this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName)); + } + } + + var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); + + var 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) + { + // Default to the threshold for best smartcabbing (makes smallest cabinet). + cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold: 0, compressionLevel, this.ModularizationSuffix /*, this.FileManager*/); + } + else // reuse the cabinet from the cabinet cache. + { + this.Messaging.Write(VerboseMessages.ReusingCabCache(mediaSymbol.SourceLineNumbers, mediaSymbol.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) + { + this.Messaging.Write(WarningMessages.CannotUpdateCabCache(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, e.Message)); + } + } + + var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaSymbol.SourceLineNumbers); + this.trackedFiles.Add(trackResolvedCabinet); + + if (mediaSymbol.Cabinet.StartsWith("#", StringComparison.Ordinal)) + { + var streamsTable = data.EnsureTable(this.TableDefinitions["_Streams"]); + + var streamRow = streamsTable.CreateRow(mediaSymbol.SourceLineNumbers); + streamRow[0] = mediaSymbol.Cabinet.Substring(1); + streamRow[1] = resolvedCabinet.Path; + } + else + { + var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaSymbol.Cabinet), TrackedFileType.Final, mediaSymbol.SourceLineNumbers); + this.trackedFiles.Add(trackDestination); + + var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaSymbol.SourceLineNumbers); + this.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 newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) + { + throw new NotImplementedException(); +#if TODO_CAB_SPANNING + // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads + var mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); + try + { + if (!mutex.WaitOne(0, false)) // Check if you can get the lock + { + // Cound not get the Lock + this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel()); + mutex.WaitOne(); // Wait on other thread + } + + var firstCabinetName = firstCabName + ".cab"; + var transferAdded = false; // Used for Error Handling + + // Create File Transfer for new Cabinet using transfer of Base Cabinet + foreach (var transfer in this.FileTransfers) + { + if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) + { + var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); + var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); + + var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); + this.trackedFiles.Add(trackSource); + + var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers); + this.trackedFiles.Add(trackTarget); + + var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); + this.fileTransfers.Add(newTransfer); + + transferAdded = true; + break; + } + } + + // Check if File Transfer was added + if (!transferAdded) + { + throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); + } + + // Add the new Cabinets to media table using LastSequence of Base Cabinet + var mediaTable = this.Output.Tables["Media"]; + var wixFileTable = this.Output.Tables["WixFile"]; + var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain + var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain + var lastSplitCabinetFound = false; // Used for Error Handling + + var 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(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); + } + } + + // Check if the last Split Cabinet was found in the Media Table + if (!lastSplitCabinetFound) + { + throw new WixException(ErrorMessages.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(); + } +#endif + } + + + /// + /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides + /// + private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) + { + // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size + var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); + var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + + // Supply Compile MediaTemplate Attributes to Cabinet Builder + if (this.MediaTemplate != null) + { + // Get the Value for Max Cab Size for File Splitting + var maxCabSizeForLargeFileInMB = 0; + try + { + // Override authored mcslfs value if environment variable is authored. + maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; + + var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; + maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); + } + + var maxPreCompressedSizeInMB = 0; + try + { + // Override authored mums value if environment variable is authored. + maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; + + var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; + maxUncompressedMediaSize = maxPreCompressedSizeInMB; + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); + } + } + else + { + maxCabSizeForLargeFileSplitting = 0; + maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..47d8399f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.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.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + + /// + /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. + /// + internal class CreateDeltaPatchesCommand + { + public CreateDeltaPatchesCommand(List fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId) + { + this.FileFacades = fileFacades; + this.IntermediateFolder = intermediateFolder; + this.WixPatchId = wixPatchId; + } + + private IEnumerable FileFacades { get; } + + private WixPatchSymbol WixPatchId { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; + var apiPatchingSymbolFlags = (PatchSymbolFlags)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); + +#if TODO_PATCHING_DELTA + 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.IntermediateFolder, String.Concat(deltaBase, ".dpf")); + string headerFile = Path.Combine(this.IntermediateFolder, 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)); + } + } + } +#endif + + throw new NotImplementedException(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs new file mode 100644 index 00000000..ff03413c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs @@ -0,0 +1,250 @@ +// 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.Globalization; + using System.IO; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class CreateIdtFileCommand + { + public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns) + { + this.Messaging = messaging; + this.Table = table; + this.Codepage = codepage; + this.IntermediateFolder = intermediateFolder; + this.KeepAddedColumns = keepAddedColumns; + } + + private IMessaging Messaging { get; } + + private Table Table { get; } + + private int Codepage { get; set; } + + private string IntermediateFolder { get; } + + private bool KeepAddedColumns { get; } + + public string IdtPath { get; private set; } + + public void Execute() + { + // write out the table to an IDT file + var encoding = GetCodepageEncoding(this.Codepage); + + this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); + + using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) + { + this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); + } + } + + private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) + { + if (table.Definition.Unreal) + { + return; + } + + if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length) + { + throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable)); + } + + // Tack on the table header, and flush before we start writing bytes directly to the stream. + var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); + writer.Write(header); + writer.Flush(); + + using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) + { + // Create an encoding that replaces characters with question marks, and doesn't throw. We'll + // use this in case of errors + Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); + + foreach (Row row in table.Rows) + { + if (row.Redundant) + { + continue; + } + + string rowString = this.RowToIdtDefinition(row, keepAddedColumns); + byte[] rowBytes; + + try + { + // GetBytes will throw an exception if any character doesn't match our current encoding + rowBytes = writer.Encoding.GetBytes(rowString); + } + catch (EncoderFallbackException) + { + this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); + + rowBytes = convertEncoding.GetBytes(rowString); + } + + binary.Write(rowBytes, 0, rowBytes.Length); + } + } + } + + private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) + { + var first = true; + var columnString = new StringBuilder(); + var dataString = new StringBuilder(); + var tableString = new StringBuilder(); + + tableString.Append(definition.Name); + foreach (var column in definition.Columns) + { + // Conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end. + if (column.Added && !keepAddedColumns) + { + break; + } + + if (column.Unreal) + { + continue; + } + + if (!first) + { + columnString.Append('\t'); + dataString.Append('\t'); + } + + columnString.Append(column.Name); + dataString.Append(ColumnIdtType(column)); + + if (column.PrimaryKey) + { + tableString.AppendFormat("\t{0}", column.Name); + } + + first = false; + } + columnString.Append("\r\n"); + columnString.Append(dataString); + columnString.Append("\r\n"); + columnString.Append(tableString); + columnString.Append("\r\n"); + + return columnString.ToString(); + } + + private string RowToIdtDefinition(Row row, bool keepAddedColumns) + { + var first = true; + var sb = new StringBuilder(); + + foreach (var field in row.Fields) + { + // Conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end. + if (field.Column.Added && !keepAddedColumns) + { + break; + } + + if (field.Column.Unreal) + { + continue; + } + + if (first) + { + first = false; + } + else + { + sb.Append('\t'); + } + + sb.Append(this.FieldToIdtValue(field)); + } + sb.Append("\r\n"); + + return sb.ToString(); + } + + private string FieldToIdtValue(Field field) + { + var data = field.AsString(); + + if (String.IsNullOrEmpty(data)) + { + return data; + } + + // Special field value idt-specific escaping. + return data.Replace('\t', '\x10') + .Replace('\r', '\x11') + .Replace('\n', '\x19'); + } + + private static Encoding GetCodepageEncoding(int codepage) + { + Encoding encoding; + + // If UTF8 encoding, use the UTF8-specific constructor to avoid writing + // the byte order mark at the beginning of the file + if (codepage == Encoding.UTF8.CodePage) + { + encoding = new UTF8Encoding(false, true); + } + else + { + if (codepage == 0) + { + codepage = Encoding.ASCII.CodePage; + } + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); + } + + return encoding; + } + + /// + /// Gets the type of the column in IDT format. + /// + /// IDT format for column type. + private static string ColumnIdtType(ColumnDefinition column) + { + char typeCharacter; + switch (column.Type) + { + case ColumnType.Number: + typeCharacter = column.Nullable ? 'I' : 'i'; + break; + case ColumnType.Preserved: + case ColumnType.String: + typeCharacter = column.Nullable ? 'S' : 's'; + break; + case ColumnType.Localized: + typeCharacter = column.Nullable ? 'L' : 'l'; + break; + case ColumnType.Object: + typeCharacter = column.Nullable ? 'V' : 'v'; + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); + } + + return String.Concat(typeCharacter, column.Length); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs new file mode 100644 index 00000000..d0e25571 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs @@ -0,0 +1,260 @@ +// 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.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Services; + + internal class CreateInstanceTransformsCommand + { + public CreateInstanceTransformsCommand(IntermediateSection section, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, IBackendHelper backendHelper) + { + this.Section = section; + this.Output = output; + this.TableDefinitions = tableDefinitions; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private WindowsInstallerData Output { get; } + + public TableDefinitionCollection TableDefinitions { get; } + + private IBackendHelper BackendHelper { get; } + + public void Execute() + { + // Create and add substorages for instance transforms. + var wixInstanceTransformsSymbols = this.Section.Symbols.OfType(); + + if (wixInstanceTransformsSymbols.Any()) + { + string targetProductCode = null; + string targetUpgradeCode = null; + string targetProductVersion = null; + + var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"]; + var targetPropertyTable = this.Output.Tables["Property"]; + + // Get the data from target database + foreach (var 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, we'll get the Components rows from the real Component table. + var targetInstanceComponentTable = this.Section.Symbols.OfType(); + var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null); + + if (instanceComponentGuids.Any()) + { + var targetComponentTable = this.Output.Tables["Component"]; + foreach (ComponentRow componentRow in targetComponentTable.Rows) + { + var component = (string)componentRow[0]; + if (instanceComponentGuids.ContainsKey(component)) + { + instanceComponentGuids[component] = componentRow; + } + } + } + + // Generate the instance transforms + foreach (var instanceSymbol in wixInstanceTransformsSymbols) + { + var instanceId = instanceSymbol.Id.Id; + + var instanceTransform = new WindowsInstallerData(instanceSymbol.SourceLineNumbers); + instanceTransform.Type = OutputType.Transform; + instanceTransform.Codepage = this.Output.Codepage; + + var instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + string targetPlatformAndLanguage = null; + + foreach (var summaryInformationRow in targetSummaryInformationTable.Rows) + { + if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE + { + targetPlatformAndLanguage = (string)summaryInformationRow[1]; + } + + // Copy the row's data to the transform. + var copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(summaryInformationRow.SourceLineNumbers); + copyOfSummaryRow[0] = summaryInformationRow[0]; + copyOfSummaryRow[1] = summaryInformationRow[1]; + } + + // Modify the appropriate properties. + var propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); + + // Change the ProductCode property + var productCode = instanceSymbol.ProductCode; + if ("*" == productCode) + { + productCode = this.BackendHelper.CreateGuid(); + } + + var productCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + productCodeRow.Operation = RowOperation.Modify; + productCodeRow.Fields[1].Modified = true; + productCodeRow[0] = "ProductCode"; + productCodeRow[1] = productCode; + + // Change the instance property + var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + instanceIdRow.Operation = RowOperation.Modify; + instanceIdRow.Fields[1].Modified = true; + instanceIdRow[0] = instanceSymbol.PropertyId; + instanceIdRow[1] = instanceId; + + if (!String.IsNullOrEmpty(instanceSymbol.ProductName)) + { + // Change the ProductName property + var productNameRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + productNameRow.Operation = RowOperation.Modify; + productNameRow.Fields[1].Modified = true; + productNameRow[0] = "ProductName"; + productNameRow[1] = instanceSymbol.ProductName; + } + + if (!String.IsNullOrEmpty(instanceSymbol.UpgradeCode)) + { + // Change the UpgradeCode property + var upgradeCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + upgradeCodeRow.Operation = RowOperation.Modify; + upgradeCodeRow.Fields[1].Modified = true; + upgradeCodeRow[0] = "UpgradeCode"; + upgradeCodeRow[1] = instanceSymbol.UpgradeCode; + + // Change the Upgrade table + var targetUpgradeTable = this.Output.Tables["Upgrade"]; + if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) + { + var upgradeId = instanceSymbol.UpgradeCode; + var upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); + foreach (var 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]) + { + var upgradeRow = upgradeTable.CreateRow(row.SourceLineNumbers); + 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 + var upgradeRemoveRow = upgradeTable.CreateRow(row.SourceLineNumbers); + 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) + { + var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); + foreach (var targetComponentRow in instanceComponentGuids.Values) + { + var guid = targetComponentRow.Guid; + if (!String.IsNullOrEmpty(guid)) + { + var instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); + instanceComponentRow.Operation = RowOperation.Modify; + instanceComponentRow.Fields[1].Modified = true; + instanceComponentRow[0] = targetComponentRow[0]; + instanceComponentRow[1] = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)); + instanceComponentRow[2] = targetComponentRow[2]; + instanceComponentRow[3] = targetComponentRow[3]; + instanceComponentRow[4] = targetComponentRow[4]; + instanceComponentRow[5] = targetComponentRow[5]; + } + } + } + + // Update the summary information + var summaryRows = new Dictionary(instanceSummaryInformationTable.Rows.Count); + foreach (var row in instanceSummaryInformationTable.Rows) + { + summaryRows[(int)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.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = targetPlatformAndLanguage; + } + else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) + { + var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = "0"; + } + else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) + { + var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + + this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs new file mode 100644 index 00000000..5c993f63 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs @@ -0,0 +1,93 @@ +// 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.Native.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class CreatePatchTransformsCommand + { + public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Intermediate = intermediate; + this.IntermediateFolder = intermediateFolder; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private Intermediate Intermediate { get; } + + private string IntermediateFolder { get; } + + public IEnumerable PatchTransforms { get; private set; } + + public IEnumerable Execute() + { + var patchTransforms = new List(); + + var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType(); + + foreach (var symbol in symbols) + { + WindowsInstallerData transform; + + if (symbol.TransformFile is null) + { + var baselineData = this.GetData(symbol.BaselineFile.Path); + var updateData = this.GetData(symbol.UpdateFile.Path); + + var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); + transform = command.Execute(); + } + else + { + var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. + + var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder); + transform = command.Execute(); + } + + patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); + } + + this.PatchTransforms = patchTransforms; + + return this.PatchTransforms; + } + + private WindowsInstallerData GetData(string path) + { + var ext = Path.GetExtension(path); + + if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) + { + using (var database = new Database(path, OpenDatabase.ReadOnly)) + { + var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. + + var isAdminImage = false; // TODO: need a better way to set this + + var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); + return command.Execute(); + } + } + else // assume .wixpdb (or .wixout) + { + var data = WindowsInstallerData.Load(path, true); + return data; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..ba7c03a0 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs @@ -0,0 +1,83 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class CreateSpecialPropertiesCommand + { + public CreateSpecialPropertiesCommand(IntermediateSection section) + { + this.Section = section; + } + + private IntermediateSection Section { get; } + + public void Execute() + { + // Create lists of the properties that contribute to the special lists of properties. + var adminProperties = new SortedSet(); + var secureProperties = new SortedSet(); + var hiddenProperties = new SortedSet(); + + foreach (var wixPropertyRow in this.Section.Symbols.OfType()) + { + if (wixPropertyRow.Admin) + { + adminProperties.Add(wixPropertyRow.PropertyRef); + } + + if (wixPropertyRow.Hidden) + { + hiddenProperties.Add(wixPropertyRow.PropertyRef); + } + + if (wixPropertyRow.Secure) + { + secureProperties.Add(wixPropertyRow.PropertyRef); + } + } + + // Hide properties for in-script custom actions that have HideTarget set. + var hideTargetCustomActions = this.Section.Symbols.OfType().Where( + ca => ca.Hidden + && (ca.ExecutionType == CustomActionExecutionType.Deferred + || ca.ExecutionType == CustomActionExecutionType.Commit + || ca.ExecutionType == CustomActionExecutionType.Rollback)) + .Select(ca => ca.Id.Id); + hiddenProperties.UnionWith(hideTargetCustomActions); + + // Ensure upgrade action properties are secure. + var actionProperties = this.Section.Symbols.OfType().Select(u => u.ActionProperty); + secureProperties.UnionWith(actionProperties); + + if (0 < adminProperties.Count) + { + this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "AdminProperties")) + { + Value = String.Join(";", adminProperties), + }); + } + + if (0 < secureProperties.Count) + { + this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "SecureCustomProperties")) + { + Value = String.Join(";", secureProperties), + }); + } + + if (0 < hiddenProperties.Count) + { + this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "MsiHiddenProperties")) + { + Value = String.Join(";", hiddenProperties) + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs new file mode 100644 index 00000000..d34ca3fe --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -0,0 +1,1621 @@ +// 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.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class CreateWindowsInstallerDataFromIRCommand + { + private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + + public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) + { + this.Messaging = messaging; + this.Section = section; + this.TableDefinitions = tableDefinitions; + this.Codepage = codepage; + this.BackendExtensions = backendExtensions; + this.BackendHelper = backendHelper; + this.GeneratedShortNames = new Dictionary>(); + } + + private IEnumerable BackendExtensions { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + private IMessaging Messaging { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private int Codepage { get; } + + private IntermediateSection Section { get; } + + private Dictionary> GeneratedShortNames { get; } + + public WindowsInstallerData Data { get; private set; } + + public WindowsInstallerData Execute() + { + this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers) + { + Codepage = this.Codepage, + Type = SectionTypeToOutputType(this.Section.Type) + }; + + this.AddSectionToData(); + + return this.Data; + } + + private void AddSectionToData() + { + var cellsByTableAndRowId = new Dictionary>(); + + foreach (var symbol in this.Section.Symbols) + { + var unknownSymbol = false; + switch (symbol.Definition.Type) + { + case SymbolDefinitionType.AppSearch: + this.AddSymbolDefaultly(symbol); + this.Data.EnsureTable(this.TableDefinitions["Signature"]); + break; + + case SymbolDefinitionType.Assembly: + this.AddAssemblySymbol((AssemblySymbol)symbol); + break; + + case SymbolDefinitionType.BBControl: + this.AddBBControlSymbol((BBControlSymbol)symbol); + break; + + case SymbolDefinitionType.Class: + this.AddClassSymbol((ClassSymbol)symbol); + break; + + case SymbolDefinitionType.Control: + this.AddControlSymbol((ControlSymbol)symbol); + break; + + case SymbolDefinitionType.ControlEvent: + this.AddControlEventSymbol((ControlEventSymbol)symbol); + break; + + case SymbolDefinitionType.Component: + this.AddComponentSymbol((ComponentSymbol)symbol); + break; + + case SymbolDefinitionType.CustomAction: + this.AddCustomActionSymbol((CustomActionSymbol)symbol); + break; + + case SymbolDefinitionType.Dialog: + this.AddDialogSymbol((DialogSymbol)symbol); + break; + + case SymbolDefinitionType.Directory: + this.AddDirectorySymbol((DirectorySymbol)symbol); + break; + + case SymbolDefinitionType.DuplicateFile: + this.AddDuplicateFileSymbol((DuplicateFileSymbol)symbol); + break; + + case SymbolDefinitionType.Environment: + this.AddEnvironmentSymbol((EnvironmentSymbol)symbol); + break; + + case SymbolDefinitionType.Error: + this.AddErrorSymbol((ErrorSymbol)symbol); + break; + + case SymbolDefinitionType.Feature: + this.AddFeatureSymbol((FeatureSymbol)symbol); + break; + + case SymbolDefinitionType.File: + this.AddFileSymbol((FileSymbol)symbol); + break; + + case SymbolDefinitionType.IniFile: + this.AddIniFileSymbol((IniFileSymbol)symbol); + break; + + case SymbolDefinitionType.IniLocator: + this.AddIniLocatorSymbol((IniLocatorSymbol)symbol); + break; + + case SymbolDefinitionType.Media: + this.AddMediaSymbol((MediaSymbol)symbol); + break; + + case SymbolDefinitionType.ModuleConfiguration: + this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol); + this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration"); + break; + + case SymbolDefinitionType.ModuleSubstitution: + this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution"); + break; + + case SymbolDefinitionType.MsiEmbeddedUI: + this.AddMsiEmbeddedUISymbol((MsiEmbeddedUISymbol)symbol); + break; + + case SymbolDefinitionType.MsiServiceConfig: + this.AddMsiServiceConfigSymbol((MsiServiceConfigSymbol)symbol); + break; + + case SymbolDefinitionType.MsiServiceConfigFailureActions: + this.AddMsiServiceConfigFailureActionsSymbol((MsiServiceConfigFailureActionsSymbol)symbol); + break; + + case SymbolDefinitionType.MoveFile: + this.AddMoveFileSymbol((MoveFileSymbol)symbol); + break; + + case SymbolDefinitionType.ProgId: + this.AddSymbolDefaultly(symbol); + this.Data.EnsureTable(this.TableDefinitions["Extension"]); + break; + + case SymbolDefinitionType.Property: + this.AddPropertySymbol((PropertySymbol)symbol); + break; + + case SymbolDefinitionType.RemoveFile: + this.AddRemoveFileSymbol((RemoveFileSymbol)symbol); + break; + + case SymbolDefinitionType.Registry: + this.AddRegistrySymbol((RegistrySymbol)symbol); + break; + + case SymbolDefinitionType.RegLocator: + this.AddRegLocatorSymbol((RegLocatorSymbol)symbol); + break; + + case SymbolDefinitionType.RemoveRegistry: + this.AddRemoveRegistrySymbol((RemoveRegistrySymbol)symbol); + break; + + case SymbolDefinitionType.ServiceControl: + this.AddServiceControlSymbol((ServiceControlSymbol)symbol); + break; + + case SymbolDefinitionType.ServiceInstall: + this.AddServiceInstallSymbol((ServiceInstallSymbol)symbol); + break; + + case SymbolDefinitionType.Shortcut: + this.AddShortcutSymbol((ShortcutSymbol)symbol); + break; + + case SymbolDefinitionType.TextStyle: + this.AddTextStyleSymbol((TextStyleSymbol)symbol); + break; + + case SymbolDefinitionType.Upgrade: + this.AddUpgradeSymbol((UpgradeSymbol)symbol); + break; + + case SymbolDefinitionType.WixAction: + this.AddWixActionSymbol((WixActionSymbol)symbol); + break; + + case SymbolDefinitionType.WixCustomTableCell: + this.IndexCustomTableCellSymbol((WixCustomTableCellSymbol)symbol, cellsByTableAndRowId); + break; + + case SymbolDefinitionType.WixEnsureTable: + this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol); + break; + + case SymbolDefinitionType.WixPackage: + this.AddWixPackageSymbol((WixPackageSymbol)symbol); + break; + + // Symbols used internally and are not added to the output. + case SymbolDefinitionType.WixBuildInfo: + case SymbolDefinitionType.WixBindUpdatedFiles: + case SymbolDefinitionType.WixComponentGroup: + case SymbolDefinitionType.WixComplexReference: + case SymbolDefinitionType.WixDeltaPatchFile: + case SymbolDefinitionType.WixDeltaPatchSymbolPaths: + case SymbolDefinitionType.WixFragment: + case SymbolDefinitionType.WixFeatureGroup: + case SymbolDefinitionType.WixInstanceComponent: + case SymbolDefinitionType.WixInstanceTransforms: + case SymbolDefinitionType.WixFeatureModules: + case SymbolDefinitionType.WixGroup: + case SymbolDefinitionType.WixMediaTemplate: + case SymbolDefinitionType.WixMerge: + case SymbolDefinitionType.WixOrdering: + case SymbolDefinitionType.WixPatchBaseline: + case SymbolDefinitionType.WixPatchFamilyGroup: + case SymbolDefinitionType.WixPatch: + case SymbolDefinitionType.WixPatchRef: + case SymbolDefinitionType.WixPatchTarget: + case SymbolDefinitionType.WixProperty: + case SymbolDefinitionType.WixProductTag: + case SymbolDefinitionType.WixSimpleReference: + case SymbolDefinitionType.WixSuppressAction: + case SymbolDefinitionType.WixSuppressModularization: + case SymbolDefinitionType.WixUI: + case SymbolDefinitionType.WixVariable: + break; + + // Already processed by LoadTableDefinitions. + case SymbolDefinitionType.WixCustomTable: + case SymbolDefinitionType.WixCustomTableColumn: + break; + + case SymbolDefinitionType.MustBeFromAnExtension: + unknownSymbol = !this.AddSymbolFromExtension(symbol); + break; + + default: + unknownSymbol = !this.AddSymbolDefaultly(symbol); + break; + } + + if (unknownSymbol) + { + this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); + } + } + + this.AddIndexedCellSymbols(cellsByTableAndRowId); + this.EnsureRequiredTables(); + this.ReportGeneratedShortFileNameConflicts(); + this.ReportIllegalTables(); + this.ReportMismatchedModularizations(); + this.ReportWindowsInstallerDataInconsistencies(); + } + + private void AddAssemblySymbol(AssemblySymbol symbol) + { + var attributes = symbol.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; + + var row = this.CreateRow(symbol, "MsiAssembly"); + row[0] = symbol.ComponentRef; + row[1] = symbol.FeatureRef; + row[2] = symbol.ManifestFileRef; + row[3] = symbol.ApplicationFileRef; + row[4] = attributes; + } + + private void AddBBControlSymbol(BBControlSymbol symbol) + { + var attributes = symbol.Attributes; + attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; + attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; + attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; + attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; + attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; + attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; + attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; + attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; + + var row = this.CreateRow(symbol, "BBControl"); + row[0] = symbol.BillboardRef; + row[1] = symbol.BBControl; + row[2] = symbol.Type; + row[3] = symbol.X; + row[4] = symbol.Y; + row[5] = symbol.Width; + row[6] = symbol.Height; + row[7] = attributes; + row[8] = symbol.Text; + } + + private void AddClassSymbol(ClassSymbol symbol) + { + var row = this.CreateRow(symbol, "Class"); + row[0] = symbol.CLSID; + row[1] = symbol.Context; + row[2] = symbol.ComponentRef; + row[3] = symbol.DefaultProgIdRef; + row[4] = symbol.Description; + row[5] = symbol.AppIdRef; + row[6] = symbol.FileTypeMask; + row[7] = symbol.IconRef; + row[8] = symbol.IconIndex; + row[9] = symbol.DefInprocHandler; + row[10] = symbol.Argument; + row[11] = symbol.FeatureRef; + row[12] = symbol.RelativePath ? (int?)1 : null; + } + + private void AddControlSymbol(ControlSymbol symbol) + { + var text = symbol.Text; + var attributes = symbol.Attributes; + attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; + attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; + attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; + attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; + attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; + attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; + attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; + attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; + + // If we're tracking disk space, and this is a non-FormatSize Text control, + // and the text attribute starts with '[' and ends with ']', add a space. + // It is not necessary for the whole string to be a property, just those + // two characters matter. + if (symbol.TrackDiskSpace && + "Text" == symbol.Type && + WindowsInstallerConstants.MsidbControlAttributesFormatSize != (attributes & WindowsInstallerConstants.MsidbControlAttributesFormatSize) && + null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) + { + text = String.Concat(text, " "); + } + + var row = this.CreateRow(symbol, "Control"); + row[0] = symbol.DialogRef; + row[1] = symbol.Control; + row[2] = symbol.Type; + row[3] = symbol.X; + row[4] = symbol.Y; + row[5] = symbol.Width; + row[6] = symbol.Height; + row[7] = attributes; + row[8] = symbol.Property; + row[9] = text; + row[10] = symbol.NextControlRef; + row[11] = symbol.Help; + } + + private void AddControlEventSymbol(ControlEventSymbol symbol) + { + var row = this.CreateRow(symbol, "ControlEvent"); + row[0] = symbol.DialogRef; + row[1] = symbol.ControlRef; + row[2] = symbol.Event; + row[3] = symbol.Argument; + row[4] = String.IsNullOrEmpty(symbol.Condition) ? "1" : symbol.Condition; + row[5] = symbol.Ordering; + } + + private void AddComponentSymbol(ComponentSymbol symbol) + { + var attributes = ComponentLocation.Either == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0; + attributes |= ComponentLocation.SourceOnly == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0; + attributes |= ComponentKeyPathType.Registry == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0; + attributes |= ComponentKeyPathType.OdbcDataSource == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0; + attributes |= symbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0; + attributes |= symbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0; + attributes |= symbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0; + attributes |= symbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0; + attributes |= symbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0; + attributes |= symbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0; + attributes |= symbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; + attributes |= symbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; + + var row = this.CreateRow(symbol, "Component"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentId; + row[2] = symbol.DirectoryRef; + row[3] = attributes; + row[4] = symbol.Condition; + row[5] = symbol.KeyPath; + } + + private void AddCustomActionSymbol(CustomActionSymbol symbol) + { + var type = symbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; + type |= symbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; + type |= symbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0; + type |= symbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0; + type |= CustomActionExecutionType.FirstSequence == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0; + type |= CustomActionExecutionType.OncePerProcess == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0; + type |= CustomActionExecutionType.ClientRepeat == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0; + type |= CustomActionExecutionType.Deferred == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0; + type |= CustomActionExecutionType.Rollback == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0; + type |= CustomActionExecutionType.Commit == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0; + type |= CustomActionSourceType.File == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0; + type |= CustomActionSourceType.Directory == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0; + type |= CustomActionSourceType.Property == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0; + type |= CustomActionTargetType.Dll == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0; + type |= CustomActionTargetType.Exe == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0; + type |= CustomActionTargetType.TextData == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0; + type |= CustomActionTargetType.JScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0; + type |= CustomActionTargetType.VBScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0; + + if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) + { + type |= symbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; + type |= symbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; + } + + var row = this.CreateRow(symbol, "CustomAction"); + row[0] = symbol.Id.Id; + row[1] = type; + row[2] = symbol.Source; + row[3] = symbol.Target; + row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; + + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + } + } + + private void AddDialogSymbol(DialogSymbol symbol) + { + var attributes = symbol.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; + attributes |= symbol.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; + attributes |= symbol.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; + attributes |= symbol.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; + attributes |= symbol.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; + attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; + attributes |= symbol.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; + attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; + attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; + attributes |= symbol.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; + attributes |= symbol.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; + + var row = this.CreateRow(symbol, "Dialog"); + row[0] = symbol.Id.Id; + row[1] = symbol.HCentering; + row[2] = symbol.VCentering; + row[3] = symbol.Width; + row[4] = symbol.Height; + row[5] = attributes; + row[6] = symbol.Title; + row[7] = symbol.FirstControlRef; + row[8] = symbol.DefaultControlRef; + row[9] = symbol.CancelControlRef; + + this.Data.EnsureTable(this.TableDefinitions["ListBox"]); + } + + private void AddDirectorySymbol(DirectorySymbol symbol) + { + (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); + + var shortName = symbol.ShortName; + var sourceShortname = symbol.SourceShortName; + + if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false)) + { + shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef); + } + + if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) + { + sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); + } + + var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName); + var targetName = CreateMsiFilename(shortName, name); + + if (String.IsNullOrEmpty(targetName)) + { + targetName = "."; + } + + var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; + + var row = this.CreateRow(symbol, "Directory"); + row[0] = symbol.Id.Id; + row[1] = parentDir; + row[2] = defaultDir; + + if (OutputType.Module == this.Data.Type) + { + var directoryId = symbol.Id.Id; + + if (WindowsInstallerStandard.IsStandardDirectory(directoryId)) + { + // If the directory table contains references to standard windows folders + // mergemod.dll will add customactions to set the MSM directory to + // the same directory as the standard windows folder and will add references to + // custom action to all the standard sequence tables. A problem will occur + // if the MSI does not have these tables as mergemod.dll does not add these + // tables to the MSI if absent. This code adds the tables in case mergemod.dll + // needs them. + this.Data.EnsureTable(this.TableDefinitions["CustomAction"]); + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + } + else + { + foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories()) + { + if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id)); + } + } + } + } + } + + private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol) + { + var name = symbol.DestinationName; + if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.DestinationShortName = this.CreateShortName(name, true, "CopyFile", symbol.ComponentRef, symbol.FileRef); + } + + var row = this.CreateRow(symbol, "DuplicateFile"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentRef; + row[2] = symbol.FileRef; + row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); + row[4] = symbol.DestinationFolder; + } + + private void AddEnvironmentSymbol(EnvironmentSymbol symbol) + { + var action = String.Empty; + var system = symbol.System ? "*" : String.Empty; + var uninstall = symbol.Permanent ? String.Empty : "-"; + var value = symbol.Value; + + switch (symbol.Action) + { + case EnvironmentActionType.Create: + action = "+"; + break; + case EnvironmentActionType.Set: + action = "="; + break; + case EnvironmentActionType.Remove: + action = "!"; + break; + } + + switch (symbol.Part) + { + case EnvironmentPartType.First: + value = String.Concat(value, symbol.Separator, "[~]"); + break; + case EnvironmentPartType.Last: + value = String.Concat("[~]", symbol.Separator, value); + break; + } + + var row = this.CreateRow(symbol, "Environment"); + row[0] = symbol.Id.Id; + row[1] = String.Concat(action, uninstall, system, symbol.Name); + row[2] = value; + row[3] = symbol.ComponentRef; + } + + private void AddErrorSymbol(ErrorSymbol symbol) + { + var row = this.CreateRow(symbol, "Error"); + row[0] = Convert.ToInt32(symbol.Id.Id); + row[1] = symbol.Message; + } + + private void AddFeatureSymbol(FeatureSymbol symbol) + { + var attributes = symbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0; + attributes |= symbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0; + attributes |= FeatureInstallDefault.FollowParent == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0; + attributes |= FeatureInstallDefault.Source == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0; + attributes |= FeatureTypicalDefault.Advertise == symbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0; + + var row = this.CreateRow(symbol, "Feature"); + row[0] = symbol.Id.Id; + row[1] = symbol.ParentFeatureRef; + row[2] = symbol.Title; + row[3] = symbol.Description; + row[4] = symbol.Display; + row[5] = symbol.Level; + row[6] = symbol.DirectoryRef; + row[7] = attributes; + } + + private void AddFileSymbol(FileSymbol symbol) + { + var name = symbol.Name; + if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortName = this.CreateShortName(name, true, "File", symbol.DirectoryRef); + + if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts)) + { + potentialConflicts = new List(); + this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts); + } + + potentialConflicts.Add(symbol); + } + + var row = (FileRow)this.CreateRow(symbol, "File"); + row.File = symbol.Id.Id; + row.Component = symbol.ComponentRef; + row.FileName = CreateMsiFilename(symbol.ShortName, name); + row.FileSize = symbol.FileSize; + row.Version = symbol.Version; + row.Language = symbol.Language; + row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here + row.Sequence = symbol.Sequence; + row.Source = symbol.Source.Path; + + var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Hidden) == FileSymbolAttributes.Hidden ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.ReadOnly) == FileSymbolAttributes.ReadOnly ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.System) == FileSymbolAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Vital) == FileSymbolAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; + row.Attributes = attributes; + + if (symbol.FontTitle != null) + { + var fontRow = this.CreateRow(symbol, "Font"); + fontRow[0] = symbol.Id.Id; + fontRow[1] = symbol.FontTitle; + } + + if (symbol.SelfRegCost.HasValue) + { + var selfRegRow = this.CreateRow(symbol, "SelfReg"); + selfRegRow[0] = symbol.Id.Id; + selfRegRow[1] = symbol.SelfRegCost.Value; + } + } + + private void AddIniFileSymbol(IniFileSymbol symbol) + { + var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile"; + + var name = symbol.FileName; + if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortFileName = this.CreateShortName(name, true, "IniFile", symbol.ComponentRef); + } + + var row = this.CreateRow(symbol, tableName); + row[0] = symbol.Id.Id; + row[1] = CreateMsiFilename(symbol.ShortFileName, name); + row[2] = symbol.DirProperty; + row[3] = symbol.Section; + row[4] = symbol.Key; + row[5] = symbol.Value; + row[6] = symbol.Action; + row[7] = symbol.ComponentRef; + } + + private void AddIniLocatorSymbol(IniLocatorSymbol symbol) + { + var name = symbol.FileName; + if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortFileName = this.CreateShortName(name, true, "IniFileSearch"); + } + + var row = this.CreateRow(symbol, "IniLocator"); + row[0] = symbol.Id.Id; + row[1] = CreateMsiFilename(symbol.ShortFileName, name); + row[2] = symbol.Section; + row[3] = symbol.Key; + row[4] = symbol.Field; + row[5] = symbol.Type; + } + + private void AddMediaSymbol(MediaSymbol symbol) + { + if (this.Section.Type != SectionType.Module) + { + var row = (MediaRow)this.CreateRow(symbol, "Media"); + row.DiskId = symbol.DiskId; + row.LastSequence = symbol.LastSequence ?? 0; + row.DiskPrompt = symbol.DiskPrompt; + row.Cabinet = symbol.Cabinet; + row.VolumeLabel = symbol.VolumeLabel; + row.Source = symbol.Source; + } + } + + private void AddModuleConfigurationSymbol(ModuleConfigurationSymbol symbol) + { + var row = this.CreateRow(symbol, "ModuleConfiguration"); + row[0] = symbol.Id.Id; + row[1] = symbol.Format; + row[2] = symbol.Type; + row[3] = symbol.ContextData; + row[4] = symbol.DefaultValue; + row[5] = (symbol.KeyNoOrphan ? WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan : 0) | + (symbol.NonNullable ? WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable : 0); + row[6] = symbol.DisplayName; + row[7] = symbol.Description; + row[8] = symbol.HelpLocation; + row[9] = symbol.HelpKeyword; + } + + private void AddMsiEmbeddedUISymbol(MsiEmbeddedUISymbol symbol) + { + var attributes = symbol.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0; + attributes |= symbol.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0; + + var row = this.CreateRow(symbol, "MsiEmbeddedUI"); + row[0] = symbol.Id.Id; + row[1] = symbol.FileName; + row[2] = attributes; + row[3] = symbol.MessageFilter; + row[4] = symbol.Source; + } + + private void AddMsiServiceConfigSymbol(MsiServiceConfigSymbol symbol) + { + var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; + events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; + events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; + + var row = this.CreateRow(symbol, "MsiServiceConfigFailureActions"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = events; + row[3] = symbol.ConfigType; + row[4] = symbol.Argument; + row[5] = symbol.ComponentRef; + } + + private void AddMsiServiceConfigFailureActionsSymbol(MsiServiceConfigFailureActionsSymbol symbol) + { + var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; + events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; + events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; + + var row = this.CreateRow(symbol, "MsiServiceConfig"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = events; + row[3] = symbol.ResetPeriod.HasValue ? symbol.ResetPeriod : null; + row[4] = symbol.RebootMessage ?? "[~]"; + row[5] = symbol.Command ?? "[~]"; + row[6] = symbol.Actions; + row[7] = symbol.DelayActions; + row[8] = symbol.ComponentRef; + } + + private void AddMoveFileSymbol(MoveFileSymbol symbol) + { + var name = symbol.DestinationName; + if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.DestinationShortName = this.CreateShortName(name, true, "MoveFile", symbol.ComponentRef); + } + + var row = this.CreateRow(symbol, "MoveFile"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentRef; + row[2] = symbol.SourceName; + row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); + row[4] = symbol.SourceFolder; + row[5] = symbol.DestFolder; + row[6] = symbol.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0; + } + + private void AddPropertySymbol(PropertySymbol symbol) + { + if (String.IsNullOrEmpty(symbol.Value)) + { + return; + } + + var row = (PropertyRow)this.CreateRow(symbol, "Property"); + row.Property = symbol.Id.Id; + row.Value = symbol.Value; + } + + private void AddRemoveFileSymbol(RemoveFileSymbol symbol) + { + var name = symbol.FileName; + if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortFileName = this.CreateShortName(name, true, "RemoveFile", symbol.ComponentRef); + } + + var installMode = symbol.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0; + installMode |= symbol.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0; + + var row = this.CreateRow(symbol, "RemoveFile"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentRef; + row[2] = CreateMsiFilename(symbol.ShortFileName, symbol.FileName); + row[3] = symbol.DirPropertyRef; + row[4] = installMode; + } + + private void AddRegistrySymbol(RegistrySymbol symbol) + { + var value = symbol.Value; + + switch (symbol.ValueType) + { + case RegistryValueType.Binary: + value = String.Concat("#x", value); + break; + case RegistryValueType.Expandable: + value = String.Concat("#%", value); + break; + case RegistryValueType.Integer: + value = String.Concat("#", value); + break; + case RegistryValueType.MultiString: + switch (symbol.ValueAction) + { + case RegistryValueActionType.Append: + value = String.Concat("[~]", value); + break; + case RegistryValueActionType.Prepend: + value = String.Concat(value, "[~]"); + break; + case RegistryValueActionType.Write: + default: + if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + { + value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + } + break; + } + break; + case RegistryValueType.String: + // escape the leading '#' character for string registry keys + if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + { + value = String.Concat("#", value); + } + break; + } + + var row = this.CreateRow(symbol, "Registry"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[4] = value; + row[5] = symbol.ComponentRef; + } + + private void AddRegLocatorSymbol(RegLocatorSymbol symbol) + { + var type = (int)symbol.Type; + type |= symbol.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0; + + var row = this.CreateRow(symbol, "RegLocator"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[4] = type; + } + + private void AddRemoveRegistrySymbol(RemoveRegistrySymbol symbol) + { + if (symbol.Action == RemoveRegistryActionType.RemoveOnInstall) + { + var row = this.CreateRow(symbol, "RemoveRegistry"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[4] = symbol.ComponentRef; + } + else // Registry table is used to remove registry keys on uninstall. + { + var row = this.CreateRow(symbol, "Registry"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[5] = symbol.ComponentRef; + } + } + + private void AddServiceControlSymbol(ServiceControlSymbol symbol) + { + var events = symbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0; + events |= symbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0; + events |= symbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0; + events |= symbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0; + events |= symbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0; + events |= symbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0; + + var row = this.CreateRow(symbol, "ServiceControl"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = events; + row[3] = symbol.Arguments; + if (symbol.Wait.HasValue) + { + row[4] = symbol.Wait.Value ? 1 : 0; + } + row[5] = symbol.ComponentRef; + } + + private void AddServiceInstallSymbol(ServiceInstallSymbol symbol) + { + var errorControl = (int)symbol.ErrorControl; + errorControl |= symbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0; + + var serviceType = (int)symbol.ServiceType; + serviceType |= symbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0; + + var row = this.CreateRow(symbol, "ServiceInstall"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = symbol.DisplayName; + row[3] = serviceType; + row[4] = (int)symbol.StartType; + row[5] = errorControl; + row[6] = symbol.LoadOrderGroup; + row[7] = symbol.Dependencies; + row[8] = symbol.StartName; + row[9] = symbol.Password; + row[10] = symbol.Arguments; + row[11] = symbol.ComponentRef; + row[12] = symbol.Description; + } + + private void AddShortcutSymbol(ShortcutSymbol symbol) + { + var name = symbol.Name; + if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortName = this.CreateShortName(name, true, "Shortcut", symbol.ComponentRef, symbol.DirectoryRef); + } + + var row = this.CreateRow(symbol, "Shortcut"); + row[0] = symbol.Id.Id; + row[1] = symbol.DirectoryRef; + row[2] = CreateMsiFilename(symbol.ShortName, name); + row[3] = symbol.ComponentRef; + row[4] = symbol.Target; + row[5] = symbol.Arguments; + row[6] = symbol.Description; + row[7] = symbol.Hotkey; + row[8] = symbol.IconRef; + row[9] = symbol.IconIndex; + row[10] = (int?)symbol.Show; + row[11] = symbol.WorkingDirectory; + row[12] = symbol.DisplayResourceDll; + row[13] = symbol.DisplayResourceId; + row[14] = symbol.DescriptionResourceDll; + row[15] = symbol.DescriptionResourceId; + } + + private void AddTextStyleSymbol(TextStyleSymbol symbol) + { + var styleBits = symbol.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; + styleBits |= symbol.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0; + styleBits |= symbol.Strike ? WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike : 0; + styleBits |= symbol.Underline ? WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline : 0; + + long? color = null; + + if (symbol.Red.HasValue || symbol.Green.HasValue || symbol.Blue.HasValue) + { + color = symbol.Red ?? 0; + color += (long)(symbol.Green ?? 0) * 256; + color += (long)(symbol.Blue ?? 0) * 65536; + } + + var row = this.CreateRow(symbol, "TextStyle"); + row[0] = symbol.Id.Id; + row[1] = symbol.FaceName; + row[2] = symbol.Size; + row[3] = color; + row[4] = styleBits == 0 ? null : (int?)styleBits; + } + + private void AddUpgradeSymbol(UpgradeSymbol symbol) + { + var row = (UpgradeRow)this.CreateRow(symbol, "Upgrade"); + row.UpgradeCode = symbol.UpgradeCode; + row.VersionMin = symbol.VersionMin; + row.VersionMax = symbol.VersionMax; + row.Language = symbol.Language; + row.Remove = symbol.Remove; + row.ActionProperty = symbol.ActionProperty; + + var attributes = symbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0; + attributes |= symbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0; + attributes |= symbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0; + attributes |= symbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0; + attributes |= symbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0; + attributes |= symbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0; + row.Attributes = attributes; + } + + private void AddWixActionSymbol(WixActionSymbol symbol) + { + // Get the table definition for the action (and ensure the proper table exists for a module). + string sequenceTableName = null; + switch (symbol.SequenceTable) + { + case SequenceTable.AdminExecuteSequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + sequenceTableName = "ModuleAdminExecuteSequence"; + } + else + { + sequenceTableName = "AdminExecuteSequence"; + } + break; + case SequenceTable.AdminUISequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + sequenceTableName = "ModuleAdminUISequence"; + } + else + { + sequenceTableName = "AdminUISequence"; + } + break; + case SequenceTable.AdvertiseExecuteSequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + sequenceTableName = "ModuleAdvtExecuteSequence"; + } + else + { + sequenceTableName = "AdvtExecuteSequence"; + } + break; + case SequenceTable.InstallExecuteSequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + sequenceTableName = "ModuleInstallExecuteSequence"; + } + else + { + sequenceTableName = "InstallExecuteSequence"; + } + break; + case SequenceTable.InstallUISequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + sequenceTableName = "ModuleInstallUISequence"; + } + else + { + sequenceTableName = "InstallUISequence"; + } + break; + } + + // create the action sequence row in the output + var row = this.CreateRow(symbol, sequenceTableName); + + if (SectionType.Module == this.Section.Type) + { + row[0] = symbol.Action; + if (0 != symbol.Sequence) + { + row[1] = symbol.Sequence; + } + else + { + var after = (null == symbol.Before); + row[2] = after ? symbol.After : symbol.Before; + row[3] = after ? 1 : 0; + } + row[4] = symbol.Condition; + } + else + { + row[0] = symbol.Action; + row[1] = symbol.Condition; + row[2] = symbol.Sequence; + } + } + + private void IndexCustomTableCellSymbol(WixCustomTableCellSymbol wixCustomTableCellSymbol, Dictionary> cellsByTableAndRowId) + { + var tableAndRowId = wixCustomTableCellSymbol.TableRef + "/" + wixCustomTableCellSymbol.RowId; + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) + { + cells = new List(); + cellsByTableAndRowId.Add(tableAndRowId, cells); + } + + cells.Add(wixCustomTableCellSymbol); + } + + private void AddIndexedCellSymbols(Dictionary> cellsByTableAndRowId) + { + foreach (var rowOfCells in cellsByTableAndRowId.Values) + { + var firstCellSymbol = rowOfCells[0]; + var customTableDefinition = this.TableDefinitions[firstCellSymbol.TableRef]; + + if (customTableDefinition.Unreal) + { + continue; + } + + var customRow = this.CreateRow(firstCellSymbol, customTableDefinition); + var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); + +#if TODO // SectionId seems like a good thing to preserve. + customRow.SectionId = symbol.SectionId; +#endif + foreach (var cell in rowOfCells) + { + var data = cell.Data; + + if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) + { + if (!String.IsNullOrEmpty(data)) + { + if (rowField.Column.Type == ColumnType.Number) + { + try + { + rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); + } + catch (OverflowException) + { + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); + } + } + else if (rowField.Column.Category == ColumnCategory.Identifier) + { + if (this.BackendHelper.IsValidIdentifier(data) || this.BackendHelper.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) + { + rowField.Data = data; + } + else + { + this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); + } + } + else + { + rowField.Data = data; + } + } + } + else + { + this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); + } + } + + for (var i = 0; i < customTableDefinition.Columns.Length; ++i) + { + if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) + { + this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellSymbol.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); + } + } + } + } + + private void AddWixEnsureTableSymbol(WixEnsureTableSymbol symbol) + { + var tableDefinition = this.TableDefinitions[symbol.Table]; + this.Data.EnsureTable(tableDefinition); + } + + private void AddWixPackageSymbol(WixPackageSymbol symbol) + { + // TODO: Remove the following from the compiler and do it here instead. + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); + //if (null != upgradeCode) + //{ + // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); + //} + + //if (isPerMachine) + //{ + // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); + //} + } + + private bool AddSymbolFromExtension(IntermediateSymbol symbol) + { + foreach (var extension in this.BackendExtensions) + { + if (extension.TryProcessSymbol(this.Section, symbol, this.Data, this.TableDefinitions)) + { + return true; + } + } + + return false; + } + + private bool AddSymbolDefaultly(IntermediateSymbol symbol) => + this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions); + + private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable) + { + var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"]; + var table = this.Data.EnsureTable(tableDefinition); + if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable)) + { + var row = this.CreateRow(symbol, tableDefinition); + row[0] = ignoredTable; + } + } + + private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) + { + var directory = symbol.Name.Trim(PathSeparatorChars); + var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); + + var start = 0; + var end = directory.IndexOfAny(PathSeparatorChars); + var path = String.Empty; + + while (start <= end) + { + var subdirectoryName = directory.Substring(start, end - start); + + if (!String.IsNullOrEmpty(subdirectoryName)) + { + path = Path.Combine(path, subdirectoryName); + + var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); + var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); + + var subdirectoryRow = this.CreateRow(symbol, "Directory"); + subdirectoryRow[0] = id; + subdirectoryRow[1] = parentDir; + subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName); + + parentDir = id; + } + + start = end + 1; + end = symbol.Name.IndexOfAny(PathSeparatorChars, start); + } + + var name = (start == 0) ? directory : directory.Substring(start); + + return (name, parentDir); + } + + private void EnsureRequiredTables() + { + // check for missing table and add them or display an error as appropriate + switch (this.Data.Type) + { + case OutputType.Module: + this.Data.EnsureTable(this.TableDefinitions["Component"]); + this.Data.EnsureTable(this.TableDefinitions["Directory"]); + this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]); + this.Data.EnsureTable(this.TableDefinitions["File"]); + this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]); + this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]); + break; + + case OutputType.PatchCreation: + var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0; + var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0; + var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0; + + if (imageFamiliesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies")); + } + + if (targetImagesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages")); + } + + if (upgradedImagesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages")); + } + + this.Data.EnsureTable(this.TableDefinitions["Properties"]); + break; + + case OutputType.Product: + this.Data.EnsureTable(this.TableDefinitions["File"]); + this.Data.EnsureTable(this.TableDefinitions["Media"]); + break; + } + } + + private void ReportGeneratedShortFileNameConflicts() + { + foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1)) + { + this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName)); + for (var i = 1; i < conflicts.Count; ++i) + { + this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers)); + } + } + } + + private void ReportIllegalTables() + { + foreach (var table in this.Data.Tables) + { + switch (this.Data.Type) + { + case OutputType.Module: + if ("BBControl" == table.Name || + "Billboard" == table.Name || + "CCPSearch" == table.Name || + "Feature" == table.Name || + "LaunchCondition" == table.Name || + "Media" == table.Name || + "Patch" == table.Name || + "Upgrade" == table.Name || + "WixMerge" == table.Name) + { + foreach (Row row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); + } + } + else if ("Error" == table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.PatchCreation: + if (!table.Definition.Unreal && + "_SummaryInformation" != table.Name && + "ExternalFiles" != table.Name && + "FamilyFileRanges" != table.Name && + "ImageFamilies" != table.Name && + "PatchMetadata" != table.Name && + "PatchSequence" != table.Name && + "Properties" != table.Name && + "TargetFiles_OptionalData" != table.Name && + "TargetImages" != table.Name && + "UpgradedFiles_OptionalData" != table.Name && + "UpgradedFilesToIgnore" != table.Name && + "UpgradedImages" != table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.Patch: + if (!table.Definition.Unreal && + "_SummaryInformation" != table.Name && + "Media" != table.Name && + "MsiFileHash" != table.Name && + "MsiPatchMetadata" != table.Name && + "MsiPatchSequence" != table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.Product: + if ("ModuleAdminExecuteSequence" == table.Name || + "ModuleAdminUISequence" == table.Name || + "ModuleAdvtExecuteSequence" == table.Name || + "ModuleAdvtUISequence" == table.Name || + "ModuleComponents" == table.Name || + "ModuleConfiguration" == table.Name || + "ModuleDependency" == table.Name || + "ModuleExclusion" == table.Name || + "ModuleIgnoreTable" == table.Name || + "ModuleInstallExecuteSequence" == table.Name || + "ModuleInstallUISequence" == table.Name || + "ModuleSignature" == table.Name || + "ModuleSubstitution" == table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); + } + } + break; + } + } + } + + private void ReportMismatchedModularizations() + { + // verify that modularization types match for foreign key relationships + foreach (var tableDefinition in this.TableDefinitions) + { + foreach (var columnDefinition in tableDefinition.Columns) + { + if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue) + { + if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition)) + { + var keyColumnIndex = columnDefinition.KeyColumn ?? -1; + + if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length) + { + this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex)); + } + else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) + { + this.Messaging.Write(WarningMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString())); + } + } + // else - ignore missing table definitions as that error is caught in other places + } + } + } + } + + private void ReportWindowsInstallerDataInconsistencies() + { + // Get the output's minimum installer version + var outputInstallerVersion = Int32.MaxValue; + + if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable)) + { + outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue; + } + + // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40). + if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["Error"]); + } + + // Check for the presence of tables/rows/columns that require MSI 1.1 or later. + if (outputInstallerVersion < 110) + { + if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable)) + { + foreach (var row in isolatedComponentTable.Rows) + { + this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); + } + } + } + + // Check for the presence of tables/rows/columns that require MSI 4.0 or later + if (outputInstallerVersion < 400) + { + if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable)) + { + foreach (var row in shortcutTable.Rows) + { + if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) + { + this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); + } + } + } + } + } + + private static OutputType SectionTypeToOutputType(SectionType type) + { + switch (type) + { + case SectionType.Bundle: + return OutputType.Bundle; + case SectionType.Module: + return OutputType.Module; + case SectionType.Product: + return OutputType.Product; + case SectionType.PatchCreation: + return OutputType.PatchCreation; + case SectionType.Patch: + return OutputType.Patch; + + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + } + + private Row CreateRow(IntermediateSymbol symbol, string tableDefinitionName) => + this.CreateRow(symbol, this.TableDefinitions[tableDefinitionName]); + + private Row CreateRow(IntermediateSymbol symbol, TableDefinition tableDefinition) => + this.BackendHelper.CreateRow(this.Section, symbol, this.Data, tableDefinition); + + + private string CreateShortName(string longName, bool keepExtension, params string[] args) + { + longName = longName.ToLowerInvariant(); + + // collect all the data + var strings = new List(1 + args.Length); + strings.Add(longName); + strings.AddRange(args); + + // prepare for hashing + var stringData = String.Join("|", strings); + var data = Encoding.UTF8.GetBytes(stringData); + + // hash the data + byte[] hash; + using (var sha1 = new SHA1CryptoServiceProvider()) + { + hash = sha1.ComputeHash(data); + } + + // generate the short file/directory name without an extension + var shortName = new StringBuilder(Convert.ToBase64String(hash)); + shortName.Length = 8; + shortName.Replace('+', '-').Replace('/', '_'); + + if (keepExtension) + { + var extension = Path.GetExtension(longName); + + if (4 < extension.Length) + { + extension = extension.Substring(0, 4); + } + + shortName.Append(extension); + + // check the generated short name to ensure its still legal (the extension may not be legal) + if (!this.BackendHelper.IsValidShortFilename(shortName.ToString(), false)) + { + // remove the extension (by truncating the generated file name back to the generated characters) + shortName.Length -= extension.Length; + } + } + + return shortName.ToString().ToLowerInvariant(); + } + + private static string CreateMsiFilename(string shortName, string longName) + { + if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase)) + { + return longName; + } + else + { + return shortName + "|" + longName; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..7c1e085c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -0,0 +1,221 @@ +// 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.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using WixToolset.Core.Native; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.Native.Msm; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Retrieve files information and extract them from merge modules. + /// + internal class ExtractMergeModuleFilesCommand + { + public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable wixMergeSymbols, IEnumerable fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.WixMergeSymbols = wixMergeSymbols; + this.FileFacades = fileFacades; + this.OutputInstallerVersion = installerVersion; + this.IntermediateFolder = intermediateFolder; + this.SuppressLayout = suppressLayout; + } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + private IEnumerable WixMergeSymbols { get; } + + private IEnumerable FileFacades { get; } + + private int OutputInstallerVersion { get; } + + private string IntermediateFolder { get; } + + private bool SuppressLayout { get; } + + public IEnumerable MergeModulesFileFacades { get; private set; } + + public void Execute() + { + var mergeModulesFileFacades = new List(); + + var 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. + var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); + + foreach (var wixMergeRow in this.WixMergeSymbols) + { + var 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(WixMergeSymbol wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) + { + var containsFiles = false; + + try + { + // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. + using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) + { + if (db.TableExists("File") && db.TableExists("Component")) + { + var uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); + + using (var 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) + foreach (var record in view.Records) + { + // 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. + var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1])); + fileSymbol.Attributes = wixMergeRow.FileAttributes; + fileSymbol.DirectoryRef = record[2]; + fileSymbol.DiskId = wixMergeRow.DiskId; + fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; + + var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + + // If case-sensitive collision with another merge module or a user-authored file identifier. + if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) + { + this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id)); + } + else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module + { + this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id)); + } + else // no collision + { + mergeModulesFileFacades.Add(mergeModuleFileFacade); + + // Keep updating the indexes as new rows are added. + indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); + uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); + } + + containsFiles = true; + } + } + } + + // Get the summary information to detect the Schema + using (var summaryInformation = new SummaryInformation(db)) + { + var moduleInstallerVersionString = summaryInformation.GetProperty(14); + + try + { + var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); + if (moduleInstallerVersion > this.OutputInstallerVersion) + { + this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion)); + } + } + catch (FormatException) + { + throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); + } + } + } + } + catch (FileNotFoundException) + { + throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + } + catch (Win32Exception) + { + throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile)); + } + + return containsFiles; + } + + private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow) + { + var moduleOpen = false; + short mergeLanguage; + + var mergeId = wixMergeRow.Id.Id; + + try + { + mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString())); + return; + } + + try + { + merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); + moduleOpen = true; + + // extract the module cabinet, then explode all of the files to a temp directory + var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); + merge.ExtractCAB(moduleCabPath); + + var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); + Directory.CreateDirectory(mergeIdPath); + + try + { + var cabinet = new Cabinet(moduleCabPath); + cabinet.Extract(mergeIdPath); + } + catch (FileNotFoundException) + { + throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); + } + catch + { + throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); + } + } + catch (COMException ce) + { + throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); + } + finally + { + if (moduleOpen) + { + merge.CloseModule(); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs new file mode 100644 index 00000000..fe65ccef --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs @@ -0,0 +1,77 @@ +// 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 WixToolset.Extensibility; + + internal class FileSystemManager + { + public FileSystemManager(IEnumerable fileSystemExtensions) + { + this.Extensions = fileSystemExtensions; + } + + private IEnumerable Extensions { get; } + + public bool CompareFiles(string firstPath, string secondPath) + { + foreach (var extension in this.Extensions) + { + var compared = extension.CompareFiles(firstPath, secondPath); + if (compared.HasValue) + { + return compared.Value; + } + } + + return BuiltinCompareFiles(firstPath, secondPath); + } + + private static bool BuiltinCompareFiles(string firstPath, string secondPath) + { + if (String.Equals(firstPath, secondPath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + using (var firstStream = File.OpenRead(firstPath)) + using (var secondStream = File.OpenRead(secondPath)) + { + if (firstStream.Length != secondStream.Length) + { + return false; + } + + // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance. + // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined. + var firstBuffer = new byte[16 * 1024]; + var secondBuffer = new byte[16 * 1024]; + + var firstReadLength = 0; + do + { + firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length); + var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length); + + if (firstReadLength != secondReadLength) + { + return false; + } + + for (var i = 0; i < firstReadLength; ++i) + { + if (firstBuffer[i] != secondBuffer[i]) + { + return false; + } + } + } while (0 < firstReadLength); + } + + return true; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs new file mode 100644 index 00000000..3cdc0c28 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs @@ -0,0 +1,262 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Set the guids for components with generatable guids and validate all are appropriately unique. + /// + internal class FinalizeComponentGuids + { + internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) + { + this.Messaging = messaging; + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + private Dictionary ComponentIdGenSeeds { get; set; } + + private ILookup FilesByComponentId { get; set; } + + private Dictionary RegistrySymbolsById { get; set; } + + private Dictionary TargetPathsByDirectoryId { get; set; } + + public void Execute() + { + var componentGuidConditions = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var guidCollisions = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var componentSymbol in this.Section.Symbols.OfType()) + { + if (componentSymbol.ComponentId == "*") + { + this.GenerateComponentGuid(componentSymbol); + } + + // Now check for GUID collisions, but we don't care about unmanaged components and + // if there's a * GUID remaining, there's already an error that explained why it + // was not replaced with a real GUID. + if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") + { + if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) + { + components = new List(); + componentGuidConditions.Add(componentSymbol.ComponentId, components); + } + + components.Add(componentSymbol); + if (components.Count > 1) + { + guidCollisions.Add(componentSymbol.ComponentId); + } + } + } + + if (guidCollisions.Count > 0) + { + this.ReportGuidCollisions(guidCollisions, componentGuidConditions); + } + } + + private void GenerateComponentGuid(ComponentSymbol componentSymbol) + { + if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) + { + this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); + return; + } + + if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) + { + var bitness = componentSymbol.Win64 ? "64" : String.Empty; + var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); + } + } + 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 (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + // If the component id generation seeds have not been indexed + // from the Directory symbols do that now. + if (this.ComponentIdGenSeeds is null) + { + // If there are any Directory symbols, build up the Component Guid + // generation seeds indexed by Directory/@Id. + this.ComponentIdGenSeeds = this.Section.Symbols.OfType() + .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) + .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); + } + + // If the file symbols have not been indexed by File's ComponentRef yet + // then do that now. + if (this.FilesByComponentId is null) + { + this.FilesByComponentId = this.Section.Symbols.OfType().ToLookup(f => f.ComponentRef); + } + + // validate component meets all the conditions to have a generated guid + var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; + var numFilesInComponent = currentComponentFiles.Count(); + string path = null; + + foreach (var fileSymbol in currentComponentFiles) + { + if (fileSymbol.Id.Id == componentSymbol.KeyPath) + { + // calculate the key file's canonical target path + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); + var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); + 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)) + { + this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); + } + + // if component has more than one file, the key path must be versioned + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.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(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); + } + } + } + + // if the rules were followed, reward with a generated guid + if (!this.Messaging.EncounteredError) + { + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); + } + } + } + + private void ReportGuidCollisions(HashSet guidCollisions, Dictionary> componentGuidConditions) + { + Dictionary fileSymbolsById = null; + + foreach (var guid in guidCollisions) + { + var collidingComponents = componentGuidConditions[guid]; + var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); + + foreach (var componentSymbol in collidingComponents) + { + string path; + string type; + + if (componentSymbol.KeyPathType == ComponentKeyPathType.File) + { + if (fileSymbolsById is null) + { + fileSymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; + type = "source path"; + } + else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; + type = "registry path"; + } + else + { + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); + type = "directory"; + } + + if (allComponentsHaveConditions) + { + this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + } + } + } + + private Dictionary ResolveDirectoryTargetPaths() + { + var directories = this.Section.Symbols.OfType().ToList(); + + var targetPathsByDirectoryId = new Dictionary(directories.Count); + + // Get the target paths for all directories. + foreach (var directory in directories) + { + // 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 (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) + { + continue; + } + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..b8cca752 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -0,0 +1,408 @@ +// 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.ComponentModel; + using System.IO; + using System.Linq; + using System.Text; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class GenerateDatabaseCommand + { + private const string IdtsSubFolder = "_idts"; + + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; + this.Data = data; + this.OutputPath = outputPath; + this.TableDefinitions = tableDefinitions; + this.IntermediateFolder = intermediateFolder; + this.KeepAddedColumns = keepAddedColumns; + this.SuppressAddingValidationRows = suppressAddingValidationRows; + this.UseSubDirectory = useSubdirectory; + } + + private IBackendHelper BackendHelper { get; } + + private FileSystemManager FileSystemManager { get; } + + /// + /// Whether to keep columns added in a transform. + /// + private bool KeepAddedColumns { get; } + + private IMessaging Messaging { get; } + + private WindowsInstallerData Data { get; } + + private string OutputPath { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string IntermediateFolder { get; } + + public List GeneratedTemporaryFiles { get; } = new List(); + + /// + /// Whether to use a subdirectory based on the database file name for intermediate files. + /// + private bool SuppressAddingValidationRows { get; } + + private bool UseSubDirectory { get; } + + public void Execute() + { + // Add the _Validation rows. + if (!this.SuppressAddingValidationRows) + { + this.AddValidationRows(); + } + + var baseDirectory = this.IntermediateFolder; + + if (this.UseSubDirectory) + { + var filename = Path.GetFileNameWithoutExtension(this.OutputPath); + baseDirectory = Path.Combine(baseDirectory, filename); + } + + var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); + + var type = OpenDatabase.CreateDirect; + + if (OutputType.Patch == this.Data.Type) + { + type |= OpenDatabase.OpenPatchFile; + } + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); + + Directory.CreateDirectory(idtFolder); + + using (var db = new Database(this.OutputPath, type)) + { + // 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.Data.Codepage) + { + this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder); + } + + this.ImportTables(db, idtFolder); + + // Insert substorages (usually transforms inside a patch or instance transforms in a package). + this.ImportSubStorages(db); + + // We're good, commit the changes to the new database. + db.Commit(); + } + } + catch (IOException e) + { + // TODO: this error message doesn't seem specific enough + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); + } + } + + private void AddValidationRows() + { + var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]); + + // Add the validation rows for real tables and columns. + foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) + { + foreach (var columnDef in table.Definition.Columns.Where(c => !c.Unreal)) + { + var row = validationTable.CreateRow(null); + + row[0] = table.Name; + + row[1] = columnDef.Name; + + if (columnDef.Nullable) + { + row[2] = "Y"; + } + else + { + row[2] = "N"; + } + + if (columnDef.MinValue.HasValue) + { + row[3] = columnDef.MinValue.Value; + } + + if (columnDef.MaxValue.HasValue) + { + row[4] = columnDef.MaxValue.Value; + } + + row[5] = columnDef.KeyTable; + + if (columnDef.KeyColumn.HasValue) + { + row[6] = columnDef.KeyColumn.Value; + } + + if (ColumnCategory.Unknown != columnDef.Category) + { + row[7] = columnDef.Category.ToString(); + } + + row[8] = columnDef.Possibilities; + + row[9] = columnDef.Description; + } + } + } + + private void ImportTables(Database db, string idtDirectory) + { + foreach (var table in this.Data.Tables) + { + var importTable = table; + var 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.Data.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 (var columnDefinition in table.Definition.Columns) + { + if (ColumnType.Object == columnDefinition.Type) + { + importTable = new Table(table.Definition); + hasBinaryColumn = true; + break; + } + } + + // Create the table via IDT import. + if ("_Streams" != importTable.Name) + { + try + { + var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns); + command.Execute(); + + var trackIdt = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); + this.GeneratedTemporaryFiles.Add(trackIdt); + + db.Import(command.IdtPath); + } + 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) + { + var query = new StringBuilder("SELECT "); + + // Build the query for the view. + var firstColumn = true; + foreach (var columnDefinition in table.Definition.Columns) + { + if (columnDefinition.Unreal) + { + continue; + } + + if (!firstColumn) + { + query.Append(","); + } + + query.AppendFormat(" `{0}`", columnDefinition.Name); + firstColumn = false; + } + query.AppendFormat(" FROM `{0}`", table.Name); + + using (var tableView = db.OpenExecuteView(query.ToString())) + { + // Import each row containing a stream + foreach (var row in table.Rows) + { + using (var record = new Record(table.Definition.Columns.Length)) + { + // Stream names are created by concatenating the name of the table with the values + // of the primary key (delimited by periods). + var streamName = new StringBuilder(); + + // the _Streams table doesn't prepend the table name (or a period) + if ("_Streams" != table.Name) + { + streamName.Append(table.Name); + } + + var needStream = false; + + for (var i = 0; i < table.Definition.Columns.Length; i++) + { + var columnDefinition = table.Definition.Columns[i]; + + if (columnDefinition.Unreal) + { + continue; + } + + switch (columnDefinition.Type) + { + case ColumnType.Localized: + case ColumnType.Preserved: + case ColumnType.String: + var str = row.FieldAsString(i); + + if (columnDefinition.PrimaryKey) + { + if (0 < streamName.Length) + { + streamName.Append("."); + } + + streamName.Append(str); + } + + record.SetString(i + 1, str); + break; + case ColumnType.Number: + record.SetInteger(i + 1, row.FieldAsInteger(i)); + break; + + case ColumnType.Object: + var path = row.FieldAsString(i); + if (null != path) + { + needStream = true; + try + { + record.SetStream(i + 1, path); + } + catch (Win32Exception e) + { + if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME + { + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); + } + else + { + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); + } + } + } + break; + } + } + + // check for a stream name that is more than 62 characters long (the maximum allowed length) + if (needStream && Database.MsiMaxStreamNameLength < streamName.Length) + { + this.Messaging.Write(ErrorMessages.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(); + } + } + } + } + + private void ImportSubStorages(Database db) + { + if (0 < this.Data.SubStorages.Count) + { + using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) + { + foreach (var subStorage in this.Data.SubStorages) + { + var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); + + // Bind the transform. + var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); + command.Execute(); + + if (this.Messaging.EncounteredError) + { + continue; + } + + // Add the storage to the database. + using (var record = new Record(2)) + { + record.SetString(1, subStorage.Name); + record.SetStream(2, transformFile); + storagesView.Modify(ModifyView.Assign, record); + } + } + } + } + } + + private void SetDatabaseCodepage(Database db, int codepage, string idtFolder) + { + // Write out the _ForceCodepage IDT file. + var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt"); + using (var 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"); + } + + var trackIdt = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary); + this.GeneratedTemporaryFiles.Add(trackIdt); + + // Try to import the table into the MSI. + try + { + db.Import(idtPath); + } + catch (WixInvalidIdtException) + { + // The IDT should always be generated correctly, so an invalid code page was given. + throw new WixException(ErrorMessages.IllegalCodepage(codepage)); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs new file mode 100644 index 00000000..faa03762 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs @@ -0,0 +1,582 @@ +// 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.Globalization; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + /// + /// Creates a transform by diffing two outputs. + /// + internal class GenerateTransformCommand + { + private const char sectionDelimiter = '/'; + private readonly IMessaging messaging; + private SummaryInformationStreams transformSummaryInfo; + + /// + /// Instantiates a new Differ class. + /// + public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) + { + this.messaging = messaging; + this.TargetOutput = targetOutput; + this.UpdatedOutput = updatedOutput; + this.PreserveUnchangedRows = preserveUnchangedRows; + this.ShowPedanticMessages = showPedanticMessages; + } + + private WindowsInstallerData TargetOutput { get; } + + private WindowsInstallerData UpdatedOutput { get; } + + private TransformFlags ValidationFlags { get; } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + private bool ShowPedanticMessages { get; } + + /// + /// Gets or sets the option to suppress keeping special rows. + /// + /// The option to suppress keeping special rows. + private bool SuppressKeepingSpecialRows { get; } + + /// + /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. + /// + /// The option to keep all rows including unchanged rows. + private bool PreserveUnchangedRows { get; } + + public WindowsInstallerData Transform { get; private set; } + + /// + /// Creates a transform by diffing two outputs. + /// + public WindowsInstallerData Execute() + { + var targetOutput = this.TargetOutput; + var updatedOutput = this.UpdatedOutput; + var validationFlags = this.ValidationFlags; + + var transform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = updatedOutput.Codepage + }; + + this.transformSummaryInfo = new SummaryInformationStreams(); + + // compare the codepages + if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); + if (null != updatedOutput.SourceLineNumbers) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); + } + } + + // compare the output types + if (targetOutput.Type != updatedOutput.Type) + { + throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); + } + + // compare the contents of the tables + foreach (var targetTable in targetOutput.Tables) + { + var updatedTable = updatedOutput.Tables[targetTable.Name]; + var operation = TableOperation.None; + + var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); + + if (TableOperation.Drop == operation) + { + var droppedTable = transform.EnsureTable(targetTable.Definition); + droppedTable.Operation = TableOperation.Drop; + } + else if (TableOperation.None == operation) + { + var modifiedTable = transform.EnsureTable(updatedTable.Definition); + foreach (var row in rows) + { + modifiedTable.Rows.Add(row); + } + } + } + + // added tables + foreach (var updatedTable in updatedOutput.Tables) + { + if (null == targetOutput.Tables[updatedTable.Name]) + { + var addedTable = transform.EnsureTable(updatedTable.Definition); + addedTable.Operation = TableOperation.Add; + + foreach (var updatedRow in updatedTable.Rows) + { + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + addedTable.Rows.Add(updatedRow); + } + } + } + + // set summary information properties + if (!this.SuppressKeepingSpecialRows) + { + var summaryInfoTable = transform.Tables["_SummaryInformation"]; + this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); + } + + this.Transform = transform; + return this.Transform; + } + + /// + /// Add a row to the using the primary key. + /// + /// The indexed rows. + /// The row to index. + private void AddIndexedRow(Dictionary index, Row row) + { + var primaryKey = row.GetPrimaryKey(); + + if (null != primaryKey) + { + if (index.TryGetValue(primaryKey, out var collisionRow)) + { +#if TODO_PATCH // This case doesn't seem like it can happen any longer. + // Overriding WixActionRows have a primary key defined and take precedence in the index. + if (row is WixActionRow actionRow) + { + // If the current row is not overridable, see if the indexed row is. + if (!actionRow.Overridable) + { + if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) + { + // The indexed key is overridable and should be replaced. + index[primaryKey] = actionRow; + } + } + + // If we got this far, the row does not need to be indexed. + return; + } +#endif + + if (this.ShowPedanticMessages) + { + this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); + } + } + else + { + index.Add(primaryKey, row); + } + } + 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 bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow) + { + comparedRow = null; + + var keepRow = false; + + if (null == targetRow ^ null == updatedRow) + { + if (null == targetRow) + { + updatedRow.Operation = RowOperation.Add; + comparedRow = updatedRow; + } + else if (null == updatedRow) + { + targetRow.Operation = RowOperation.Delete; + 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; + } + } + else + { + if (this.PreserveUnchangedRows) + { + keepRow = true; + } + + for (var i = 0; i < updatedRow.Fields.Length; i++) + { + var columnDefinition = updatedRow.Fields[i].Column; + + if (!columnDefinition.PrimaryKey) + { + var 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 = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i)); + } + } + else if (ColumnType.Preserved == columnDefinition.Type) + { + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); + + // keep rows containing preserved fields so the historical data is available to the binder + keepRow = !this.SuppressKeepingSpecialRows; + } + else if (ColumnType.Object == columnDefinition.Type) + { + var targetObjectField = (ObjectField)targetRow.Fields[i]; + var 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 = targetObjectField.UnresolvedData; + + // keep rows containing object fields so the files can be compared in the binder + keepRow = !this.SuppressKeepingSpecialRows; + } + else + { + modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i)); + } + + if (modified) + { + if (null != updatedRow.Fields[i].PreviousData) + { + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); + } + + updatedRow.Fields[i].Modified = true; + updatedRow.Operation = RowOperation.Modify; + keepRow = true; + } + } + } + + if (keepRow) + { + comparedRow = updatedRow; + comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + } + } + } + + return keepRow; + } + + private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) + { + var 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 + { + var updatedPrimaryKeys = new Dictionary(); + var targetPrimaryKeys = new Dictionary(); + + // compare the table definitions + if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) + { + // continue to the next table; may be more mismatches + this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); + } + else + { + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); + + // diff the target and updated rows + foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) + { + var targetPrimaryKey = targetPrimaryKeyEntry.Key; + var targetRow = targetPrimaryKeyEntry.Value; + updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow); + + var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared); + + if (keepRow) + { + rows.Add(compared); + } + } + + // find the inserted rows + foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys) + { + var updatedPrimaryKey = updatedPrimaryKeyEntry.Key; + + if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey)) + { + var updatedRow = updatedPrimaryKeyEntry.Value; + + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + rows.Add(updatedRow); + } + } + } + } + + return rows; + } + + private void IndexPrimaryKeys(Table targetTable, Dictionary targetPrimaryKeys, Table updatedTable, Dictionary updatedPrimaryKeys) + { + // index the target rows + foreach (var row in targetTable.Rows) + { + this.AddIndexedRow(targetPrimaryKeys, row); + + if ("Property" == targetTable.Name) + { + var id = row.FieldAsString(0); + + if ("ProductCode" == id) + { + this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1); + + if ("*" == this.transformSummaryInfo.TargetProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == id) + { + this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1); + } + else if ("UpgradeCode" == id) + { + this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1); + } + } + else if ("_SummaryInformation" == targetTable.Name) + { + var id = row.FieldAsInteger(0); + + if (1 == id) // PID_CODEPAGE + { + this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1); + } + else if (7 == id) // PID_TEMPLATE + { + this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1); + } + else if (14 == id) // PID_PAGECOUNT + { + this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1); + } + } + } + + // index the updated rows + foreach (var row in updatedTable.Rows) + { + this.AddIndexedRow(updatedPrimaryKeys, row); + + if ("Property" == updatedTable.Name) + { + var id = row.FieldAsString(0); + + if ("ProductCode" == id) + { + this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1); + + if ("*" == this.transformSummaryInfo.UpdatedProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == id) + { + this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1); + } + } + else if ("_SummaryInformation" == updatedTable.Name) + { + var id = row.FieldAsInteger(0); + + if (1 == id) // PID_CODEPAGE + { + this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1); + } + else if (7 == id) // PID_TEMPLATE + { + this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1); + } + else if (14 == id) // PID_PAGECOUNT + { + this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1); + } + } + } + } + + private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) + { + // calculate the minimum version of MSI required to process the transform + var minimumVersion = 100; + + if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin)) + { + minimumVersion = Math.Max(targetMin, updatedMin); + } + + var summaryRows = new Dictionary(summaryInfoTable.Rows.Count); + + foreach (var row in summaryInfoTable.Rows) + { + var id = row.FieldAsInteger(0); + + summaryRows[id] = row; + + if ((int)SummaryInformation.Transform.CodePage == id) + { + row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; + row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id) + { + row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id) + { + row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == id) + { + 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 == id) + { + row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Security == id) + { + row[1] = "4"; + } + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; + summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) + { + var 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/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs new file mode 100644 index 00000000..949d5e18 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs @@ -0,0 +1,157 @@ +// 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.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class GetFileFacadesCommand + { + public GetFileFacadesCommand(IntermediateSection section, IWindowsInstallerBackendHelper backendHelper) + { + this.Section = section; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + public List FileFacades { get; private set; } + + public void Execute() + { + var facades = new List(); + + var assemblyFile = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); +#if TODO_PATCHING_DELTA + //var deltaPatchFiles = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); +#endif + + foreach (var file in this.Section.Symbols.OfType()) + { + assemblyFile.TryGetValue(file.Id.Id, out var assembly); + +#if TODO_PATCHING_DELTA + //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); + // TODO: should we be passing along delta information to the file facade? Probably, right? +#endif + var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); + + facades.Add(fileFacade); + } + +#if TODO_PATCHING_DELTA + this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); +#endif + + this.FileFacades = facades; + } + +#if TODO_PATCHING_DELTA + /// + /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. + /// + public void ResolveDeltaPatchSymbolPaths(Dictionary deltaPatchFiles, IEnumerable facades) + { + ILookup filesByComponent = null; + ILookup filesByDirectory = null; + ILookup filesByDiskId = null; + + foreach (var row in this.Section.Symbols.OfType().OrderBy(r => r.SymbolType)) + { + switch (row.SymbolType) + { + case SymbolPathType.File: + this.MergeSymbolPaths(row, deltaPatchFiles[row.SymbolId]); + break; + + case SymbolPathType.Component: + if (null == filesByComponent) + { + filesByComponent = facades.ToLookup(f => f.File.ComponentRef); + } + + foreach (var facade in filesByComponent[row.SymbolId]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); + } + break; + + case SymbolPathType.Directory: + if (null == filesByDirectory) + { + filesByDirectory = facades.ToLookup(f => f.File.DirectoryRef); + } + + foreach (var facade in filesByDirectory[row.SymbolId]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); + } + break; + + case SymbolPathType.Media: + if (null == filesByDiskId) + { + filesByDiskId = facades.ToLookup(f => f.File.DiskId.ToString(CultureInfo.InvariantCulture)); + } + + foreach (var facade in filesByDiskId[row.SymbolId]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); + } + break; + + case SymbolPathType.Product: + foreach (var fileRow in deltaPatchFiles.Values) + { + this.MergeSymbolPaths(row, fileRow); + } + break; + + default: + // error + break; + } + } + } + + /// + /// 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(WixDeltaPatchSymbolPathsSymbol row, WixDeltaPatchFileSymbol file) + { + if (file.SymbolPaths is null) + { + file.SymbolPaths = row.SymbolPaths; + } + else + { + file.SymbolPaths = String.Concat(file.SymbolPaths, ";", 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); + } + } + } +#endif + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs new file mode 100644 index 00000000..2ac563ac --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs @@ -0,0 +1,174 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class GetFileFacadesFromTransforms + { + public GetFileFacadesFromTransforms(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, FileSystemManager fileSystemManager, IEnumerable subStorages) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; + this.SubStorages = subStorages; + } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + private FileSystemManager FileSystemManager { get; } + + private IEnumerable SubStorages { get; } + + public List FileFacades { get; private set; } + + public void Execute() + { + var allFileRows = new List(); + + var patchMediaFileRows = new Dictionary>(); + + //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); + + // Index paired transforms by name without their "#" prefix. + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); + + // Enumerate through main transforms. + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) + { + var mainTransform = substorage.Data; + var mainFileTable = mainTransform.Tables["File"]; + + if (null == mainFileTable) + { + continue; + } + + // Index File table of pairedTransform + var pairedTransform = pairedTransforms["#" + substorage.Name]; + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) + { + var mainFileId = mainFileRow.File; + + // We need compare the underlying files and include all file changes. + var objectField = (ObjectField)mainFileRow.Fields[9]; + var pairedFileRow = pairedFileRows.Get(mainFileId); + + // If the file is new, we always need to add it to the patch. + if (mainFileRow.Operation == RowOperation.Add) + { + if (null != pairedFileRow) // RowOperation.Add + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Add; + } + } + else + { + // 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.FileSystemManager.CompareFiles(objectField.PreviousData, 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 |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.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 (var field in mainFileRow.Fields) + { + field.Modified = false; + } + + if (null != pairedFileRow) + { + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = false; + pairedFileRow.Operation = RowOperation.None; + } + continue; + } + } + } + + // index patch files by diskId+fileId + var diskId = mainFileRow.DiskId; + + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) + { + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); + } + + var patchFileRow = mediaFileRows.Get(mainFileId); + + if (null == patchFileRow) + { + //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); + mainFileRow.CopyTo(patchFileRow); + + mediaFileRows.Add(patchFileRow); + +#if TODO_PATCHING_DELTA + // TODO: should we be passing along delta information to the file facade? Probably, right? +#endif + var fileFacade = this.BackendHelper.CreateFileFacade(patchFileRow); + + allFileRows.Add(fileFacade); + } + 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, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source)); + } + +#if TODO_PATCHING_DELTA + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. + patchFileRow.AppendPreviousDataFrom(mainFileRow); +#endif + } + } + } + + this.FileFacades = allFileRows; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs new file mode 100644 index 00000000..2eb95bc5 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -0,0 +1,215 @@ +// 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.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class LoadTableDefinitionsCommand + { + public LoadTableDefinitionsCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions) + { + this.Messaging = messaging; + this.Section = section; + this.BackendExtensions = backendExtensions; + } + + public IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IEnumerable BackendExtensions { get; } + + public TableDefinitionCollection TableDefinitions { get; private set; } + + public TableDefinitionCollection Execute() + { + var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + var customColumnsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + if (customColumnsById.Any()) + { + foreach (var symbol in this.Section.Symbols.OfType()) + { + var customTableDefinition = this.CreateCustomTable(symbol, customColumnsById); + tableDefinitions.Add(customTableDefinition); + } + } + + foreach (var backendExtension in this.BackendExtensions) + { + foreach (var tableDefinition in backendExtension.TableDefinitions) + { + if (tableDefinitions.Contains(tableDefinition.Name)) + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(backendExtension.GetType().Assembly.Location, tableDefinition.Name)); + } + + tableDefinitions.Add(tableDefinition); + } + } + + this.TableDefinitions = tableDefinitions; + return this.TableDefinitions; + } + + private TableDefinition CreateCustomTable(WixCustomTableSymbol symbol, Dictionary customColumnsById) + { + var columnNames = symbol.ColumnNamesSeparated; + var columns = new List(columnNames.Length); + + foreach (var name in columnNames) + { + var column = customColumnsById[symbol.Id.Id + "/" + name]; + + var type = ColumnType.Unknown; + + if (column.Type == IntermediateFieldType.String) + { + type = column.Localizable ? ColumnType.Localized : ColumnType.String; + } + else if (column.Type == IntermediateFieldType.Number) + { + type = ColumnType.Number; + } + else if (column.Type == IntermediateFieldType.Path) + { + type = ColumnType.Object; + } + + var category = ColumnCategory.Unknown; + switch (column.Category) + { + case WixCustomTableColumnCategoryType.Text: + category = ColumnCategory.Text; + break; + case WixCustomTableColumnCategoryType.UpperCase: + category = ColumnCategory.UpperCase; + break; + case WixCustomTableColumnCategoryType.LowerCase: + category = ColumnCategory.LowerCase; + break; + case WixCustomTableColumnCategoryType.Integer: + category = ColumnCategory.Integer; + break; + case WixCustomTableColumnCategoryType.DoubleInteger: + category = ColumnCategory.DoubleInteger; + break; + case WixCustomTableColumnCategoryType.TimeDate: + category = ColumnCategory.TimeDate; + break; + case WixCustomTableColumnCategoryType.Identifier: + category = ColumnCategory.Identifier; + break; + case WixCustomTableColumnCategoryType.Property: + category = ColumnCategory.Property; + break; + case WixCustomTableColumnCategoryType.Filename: + category = ColumnCategory.Filename; + break; + case WixCustomTableColumnCategoryType.WildCardFilename: + category = ColumnCategory.WildCardFilename; + break; + case WixCustomTableColumnCategoryType.Path: + category = ColumnCategory.Path; + break; + case WixCustomTableColumnCategoryType.Paths: + category = ColumnCategory.Paths; + break; + case WixCustomTableColumnCategoryType.AnyPath: + category = ColumnCategory.AnyPath; + break; + case WixCustomTableColumnCategoryType.DefaultDir: + category = ColumnCategory.DefaultDir; + break; + case WixCustomTableColumnCategoryType.RegPath: + category = ColumnCategory.RegPath; + break; + case WixCustomTableColumnCategoryType.Formatted: + category = ColumnCategory.Formatted; + break; + case WixCustomTableColumnCategoryType.FormattedSddl: + category = ColumnCategory.FormattedSDDLText; + break; + case WixCustomTableColumnCategoryType.Template: + category = ColumnCategory.Template; + break; + case WixCustomTableColumnCategoryType.Condition: + category = ColumnCategory.Condition; + break; + case WixCustomTableColumnCategoryType.Guid: + category = ColumnCategory.Guid; + break; + case WixCustomTableColumnCategoryType.Version: + category = ColumnCategory.Version; + break; + case WixCustomTableColumnCategoryType.Language: + category = ColumnCategory.Language; + break; + case WixCustomTableColumnCategoryType.Binary: + category = ColumnCategory.Binary; + break; + case WixCustomTableColumnCategoryType.CustomSource: + category = ColumnCategory.CustomSource; + break; + case WixCustomTableColumnCategoryType.Cabinet: + category = ColumnCategory.Cabinet; + break; + case WixCustomTableColumnCategoryType.Shortcut: + category = ColumnCategory.Shortcut; + break; + case null: + default: + break; + } + + var modularization = ColumnModularizeType.None; + + switch (column.Modularize) + { + case null: + case WixCustomTableColumnModularizeType.None: + modularization = ColumnModularizeType.None; + break; + case WixCustomTableColumnModularizeType.Column: + modularization = ColumnModularizeType.Column; + break; + case WixCustomTableColumnModularizeType.CompanionFile: + modularization = ColumnModularizeType.CompanionFile; + break; + case WixCustomTableColumnModularizeType.Condition: + modularization = ColumnModularizeType.Condition; + break; + case WixCustomTableColumnModularizeType.ControlEventArgument: + modularization = ColumnModularizeType.ControlEventArgument; + break; + case WixCustomTableColumnModularizeType.ControlText: + modularization = ColumnModularizeType.ControlText; + break; + case WixCustomTableColumnModularizeType.Icon: + modularization = ColumnModularizeType.Icon; + break; + case WixCustomTableColumnModularizeType.Property: + modularization = ColumnModularizeType.Property; + break; + case WixCustomTableColumnModularizeType.SemicolonDelimited: + modularization = ColumnModularizeType.SemicolonDelimited; + break; + } + + var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal); + columns.Add(columnDefinition); + } + + var customTable = new TableDefinition(symbol.Id.Id, null, columns, symbol.Unreal); + return customTable; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..6446692e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -0,0 +1,331 @@ +// 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.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.Native.Msm; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Merge modules into the database at output path. + /// + internal class MergeModulesCommand + { + public MergeModulesCommand(IMessaging messaging, IEnumerable fileFacades, IntermediateSection section, IEnumerable suppressedTableNames, string outputPath, string intermediateFolder) + { + this.Messaging = messaging; + this.FileFacades = fileFacades; + this.Section = section; + this.SuppressedTableNames = suppressedTableNames ?? Array.Empty(); + this.OutputPath = outputPath; + this.IntermediateFolder = intermediateFolder; + } + + private IMessaging Messaging { get; } + + private IEnumerable FileFacades { get; } + + private IntermediateSection Section { get; } + + private IEnumerable SuppressedTableNames { get; } + + private string OutputPath { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + var wixMergeSymbols = this.Section.Symbols.OfType().ToList(); + if (!wixMergeSymbols.Any()) + { + return; + } + + IMsmMerge2 merge = null; + var commit = true; + var logOpen = false; + var databaseOpen = false; + var logPath = Path.Combine(this.IntermediateFolder, "merge.log"); + + try + { + merge = MsmInterop.GetMsmMerge(); + + merge.OpenLog(logPath); + logOpen = true; + + merge.OpenDatabase(this.OutputPath); + databaseOpen = true; + + var featureModulesByMergeId = this.Section.Symbols.OfType().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); + + // process all the merge rows + foreach (var wixMergeRow in wixMergeSymbols) + { + var moduleOpen = false; + + try + { + short mergeLanguage; + + try + { + mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.Language.ToString())); + continue; + } + + this.Messaging.Write(VerboseMessages.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. + this.Messaging.Write(VerboseMessages.MergingMergeModule(wixMergeRow.SourceFile)); + merge.MergeEx(wixMergeRow.FeatureRef, wixMergeRow.DirectoryRef, callback); + + // Connect any non-primary features. + if (featureModulesByMergeId.TryGetValue(wixMergeRow.Id.Id, out var featureModules)) + { + foreach (var featureModule in featureModules) + { + this.Messaging.Write(VerboseMessages.ConnectingMergeModule(wixMergeRow.SourceFile, featureModule.FeatureRef)); + merge.Connect(featureModule.FeatureRef); + } + } + } + catch (COMException) + { + commit = false; + } + finally + { + var mergeErrors = merge.Errors; + + // display all the errors encountered during the merge operations for this module + for (var i = 1; i <= mergeErrors.Count; i++) + { + var mergeError = mergeErrors[i]; + var databaseKeys = new StringBuilder(); + var moduleKeys = new StringBuilder(); + + // build a string of the database keys + for (var 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 (var 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: + this.Messaging.Write(ErrorMessages.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleKeys.ToString())); + break; + case MsmErrorType.msmErrorFeatureRequired: + this.Messaging.Write(ErrorMessages.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id.Id)); + break; + case MsmErrorType.msmErrorLanguageFailed: + this.Messaging.Write(ErrorMessages.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorLanguageUnsupported: + this.Messaging.Write(ErrorMessages.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorResequenceMerge: + this.Messaging.Write(WarningMessages.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorTableMerge: + if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table + { + this.Messaging.Write(WarningMessages.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); + } + break; + case MsmErrorType.msmErrorPlatformMismatch: + this.Messaging.Write(ErrorMessages.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + break; + default: + this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "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}'", Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); + break; + } + } + + if (0 >= mergeErrors.Count && !commit) + { + this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'", 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 (this.Messaging.EncounteredError) + { + return; + } + + using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) + { + // Suppress individual actions. + foreach (var suppressAction in this.Section.Symbols.OfType()) + { + var tableName = suppressAction.SequenceTable.WindowsInstallerTableName(); + if (db.TableExists(tableName)) + { + var query = $"SELECT * FROM {tableName} WHERE `Action` = '{suppressAction.Action}'"; + + using (var view = db.OpenExecuteView(query)) + using (var record = view.Fetch()) + { + if (null != record) + { + this.Messaging.Write(WarningMessages.SuppressMergedAction(suppressAction.Action, tableName)); + view.Modify(ModifyView.Delete, record); + } + } + } + } + + // Query for merge module actions in suppressed sequences and drop them. + foreach (var tableName in this.SuppressedTableNames) + { + if (!db.TableExists(tableName)) + { + continue; + } + + using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) + { + foreach (var resultRecord in view.Records) + { + this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName)); + } + } + + // drop suppressed sequences + using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) + { + } + + // delete the validation rows + using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) + using (var record = new Record(1)) + { + record.SetString(1, tableName); + view.Execute(record); + } + } + + // now update the Attributes column for the files from the Merge Modules + this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); + using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) + { + foreach (var file in this.FileFacades) + { + if (!file.FromModule) + { + continue; + } + + using (var record = new Record(1)) + { + record.SetString(1, file.Id); + view.Execute(record); + } + + using (var 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.Sequence); + + // Update the file attributes to match the compression specified + // on the Merge element or on the Package element. + var attributes = 0; + + // Get the current value if its not null. + if (!recordUpdate.IsNull(2)) + { + attributes = recordUpdate.GetInteger(2); + } + + if (file.Compressed) + { + attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed; + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + } + else if (file.Uncompressed) + { + attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; + } + else // clear all compression bits. + { + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + } + + recordUpdate.SetInteger(2, attributes); + + view.Modify(ModifyView.Update, recordUpdate); + } + } + } + + db.Commit(); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs new file mode 100644 index 00000000..04f1b771 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs @@ -0,0 +1,236 @@ +// 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.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class ModularizeCommand + { + public ModularizeCommand(IBackendHelper backendHelper, WindowsInstallerData output, string modularizationSuffix, IEnumerable suppressSymbols) + { + this.BackendHelper = backendHelper; + this.Output = output; + this.ModularizationSuffix = modularizationSuffix; + + // Gather all the unique suppress modularization identifiers. + this.SuppressModularizationIdentifiers = new HashSet(suppressSymbols.Select(s => s.SuppressIdentifier)); + } + + private IBackendHelper BackendHelper { get; } + + private WindowsInstallerData Output { get; } + + private string ModularizationSuffix { get; } + + private HashSet SuppressModularizationIdentifiers { get; } + + public void Execute() + { + foreach (var table in this.Output.Tables) + { + this.ModularizeTable(table); + } + } + + private void ModularizeTable(Table table) + { + var modularizedColumns = new List(); + + // find the modularized columns + for (var i = 0; i < table.Definition.Columns.Length; ++i) + { + if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) + { + modularizedColumns.Add(i); + } + } + + if (0 < modularizedColumns.Count) + { + foreach (var row in table.Rows) + { + foreach (var modularizedColumn in modularizedColumns) + { + var field = row.Fields[modularizedColumn]; + + if (field.Data != null) + { + field.Data = this.ModularizedRowFieldValue(row, field); + } + } + } + } + } + + private string ModularizedRowFieldValue(Row row, Field field) + { + var fieldData = field.AsString(); + + if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) + { + var modularizeType = field.Column.ModularizeType; + + // special logic for the ControlEvent table's Argument column + // this column requires different modularization methods depending upon the value of the Event column + if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) + { + switch (row[2].ToString()) + { + case "CheckExistingTargetPath": // redirectable property name + case "CheckTargetPath": + case "DoAction": // custom action name + case "NewDialog": // dialog name + case "SelectionBrowse": + case "SetTargetPath": + case "SpawnDialog": + case "SpawnWaitDialog": + if (this.BackendHelper.IsValidIdentifier(fieldData)) + { + modularizeType = ColumnModularizeType.Column; + } + else + { + modularizeType = ColumnModularizeType.Property; + } + break; + default: // formatted + modularizeType = ColumnModularizeType.Property; + break; + } + } + else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) + { + // icons are stored in the Binary table, so they get column-type modularization + if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && this.BackendHelper.IsValidIdentifier(fieldData)) + { + modularizeType = ColumnModularizeType.Column; + } + else + { + modularizeType = ColumnModularizeType.Property; + } + } + + switch (modularizeType) + { + case ColumnModularizeType.Column: + // ensure the value is an identifier (otherwise it shouldn't be modularized this way) + if (!this.BackendHelper.IsValidIdentifier(fieldData)) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); + } + + // if we're not supposed to suppress modularization of this identifier + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + fieldData = String.Concat(fieldData, this.ModularizationSuffix); + } + break; + + case ColumnModularizeType.Property: + case ColumnModularizeType.Condition: + Regex regex; + if (ColumnModularizeType.Property == modularizeType) + { + regex = new Regex(@"\[(?[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); + } + else + { + Debug.Assert(ColumnModularizeType.Condition == modularizeType); + + // This heinous looking regular expression is actually quite an elegant way + // to shred the entire condition into the identifiers that need to be + // modularized. Let's break it down piece by piece: + // + // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the + // regular expression is case insensitive so we don't have to worry about + // all the permutations of these strings. + // 2. Look for quoted strings. Quoted strings are just text and are ignored + // outright. + // 3. Look for environment variables. These look like identifiers we might + // otherwise be interested in but start with a percent sign. Like quoted + // strings these enviroment variable references are ignored outright. + // 4. Match all identifiers that are things that need to be modularized. Note + // the special characters (!, $, ?, &) that denote Component and Feature states. + regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); + + // less performant version of the above with captures showing where everything lives + // regex = new Regex(@"(?NOT|EQV|XOR|OR|AND|IMP)|(?"".*?"")|(?%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); + } + + var matches = regex.Matches(fieldData); + + var sb = new StringBuilder(fieldData); + + // Notice how this code walks backward through the list + // because it modifies the string as we through it. + for (var i = matches.Count - 1; 0 <= i; i--) + { + var group = matches[i].Groups["identifier"]; + if (group.Success) + { + var identifier = group.Value; + if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) + { + sb.Insert(group.Index + group.Length, this.ModularizationSuffix); + } + } + } + + fieldData = sb.ToString(); + break; + + case ColumnModularizeType.CompanionFile: + // if we're not supposed to ignore this identifier and the value does not start with + // a digit, we must have a companion file so modularize it + if (!this.SuppressModularizationIdentifiers.Contains(fieldData) && + 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) + { + fieldData = String.Concat(fieldData, this.ModularizationSuffix); + } + break; + + case ColumnModularizeType.Icon: + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); + if (-1 == start) + { + fieldData = String.Concat(fieldData, this.ModularizationSuffix); + } + else + { + fieldData = String.Concat(fieldData.Substring(0, start), this.ModularizationSuffix, fieldData.Substring(start)); + } + } + break; + + case ColumnModularizeType.SemicolonDelimited: + var keys = fieldData.Split(';'); + for (var i = 0; i < keys.Length; ++i) + { + if (!String.IsNullOrEmpty(keys[i])) + { + keys[i] = String.Concat(keys[i], this.ModularizationSuffix); + } + } + + fieldData = String.Join(";", keys); + break; + } + } + + return fieldData; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs new file mode 100644 index 00000000..5dd4d3ea --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs @@ -0,0 +1,119 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class OptimizeFileFacadesOrderCommand + { + public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List fileFacades) + { + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; + this.FileFacades = fileFacades; + } + + public List FileFacades { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + public List Execute() + { + var canonicalComponentTargetPaths = this.ComponentTargetPaths(); + + this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module)); + + return this.FileFacades; + } + + private Dictionary ComponentTargetPaths() + { + var directories = this.ResolveDirectories(); + + var canonicalPathsByDirectoryId = new Dictionary(); + foreach (var component in this.Section.Symbols.OfType()) + { + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform); + canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath); + } + + return canonicalPathsByDirectoryId; + } + + private Dictionary ResolveDirectories() + { + var targetPathsByDirectoryId = new Dictionary(); + + // Get the target paths for all directories. + foreach (var directory in this.Section.Symbols.OfType()) + { + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + + private class FileFacadeOptimizer : IComparer + { + public FileFacadeOptimizer(Dictionary componentTargetPaths, bool optimizingMergeModule) + { + this.ComponentTargetPaths = componentTargetPaths; + this.OptimizingMergeModule = optimizingMergeModule; + } + + private Dictionary ComponentTargetPaths { get; } + + private bool OptimizingMergeModule { get; } + + public int Compare(IFileFacade x, IFileFacade y) + { + // First group files by DiskId but ignore if processing a Merge Module + // because Merge Modules don't have separate disks. + var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId); + + if (compare != 0) + { + return compare; + } + + // Next try to group files by target install directory. + if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) && + this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY)) + { + compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal); + + if (compare != 0) + { + return compare; + } + } + + // TODO: Consider sorting these facades even smarter by file size or file extension + // or other creative ideas to get optimal install speed out of MSI. + compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal); + + if (compare != 0) + { + return compare; + } + + return String.Compare(x.Id, y.Id, StringComparison.Ordinal); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs new file mode 100644 index 00000000..4d849753 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.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.WindowsInstaller.Bind +{ + using WixToolset.Data.WindowsInstaller; + + internal class PatchTransform + { + public PatchTransform(string baseline, WindowsInstallerData transform) + { + this.Baseline = baseline; + this.Transform = transform; + } + + public string Baseline { get; } + + public WindowsInstallerData Transform { get; } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs new file mode 100644 index 00000000..1bd2a427 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs @@ -0,0 +1,114 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class ProcessDependencyReferencesCommand + { + // The root registry key for the dependency extension. We write to Software\Classes explicitly + // based on the current security context instead of HKCR. See + // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. + private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; + private const string RegistryDependents = "Dependents"; + + public ProcessDependencyReferencesCommand(IBackendHelper backendHelper, IntermediateSection section, IEnumerable dependencyRefSymbols) + { + this.BackendHelper = backendHelper; + this.Section = section; + this.DependencyRefSymbols = dependencyRefSymbols; + } + + private IBackendHelper BackendHelper { get; } + + private IntermediateSection Section { get; } + + private IEnumerable DependencyRefSymbols { get; } + + public void Execute() + { + var wixDependencyRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); + var wixDependencyProviderRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); + + // For each relationship, get the provides and requires rows to generate registry values. + foreach (var wixDependencyRefRow in this.DependencyRefSymbols) + { + var providesId = wixDependencyRefRow.WixDependencyProviderRef; + var requiresId = wixDependencyRefRow.WixDependencyRef; + + // If we do not find both symbols, skip the registry key generation. + if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow)) + { + continue; + } + + if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow)) + { + continue; + } + + // Format the root registry key using the required provider key and the current provider key. + var requiresKey = wixDependencyRow.Id.Id; + var providesKey = wixDependencyRow.ProviderKey; + var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey); + + // Get the component ID from the provider. + var componentId = wixDependencyProviderRow.ParentRef; + + var id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "(Default)"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "*", + }); + + if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion)) + { + id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MinVersion"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "MinVersion", + Value = wixDependencyRow.MinVersion + }); + } + + var maxVersion = (string)wixDependencyRow[3]; + if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion)) + { + id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "MaxVersion", + Value = wixDependencyRow.MaxVersion + }); + } + + if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None) + { + id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "Attributes"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "Attributes", + Value = String.Concat("#", (int)wixDependencyRow.Attributes) + }); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs new file mode 100644 index 00000000..9a068603 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs @@ -0,0 +1,131 @@ +// 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 System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class ProcessPackageSoftwareTagsCommand + { + public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags, string intermediateFolder) + { + this.Section = section; + this.SoftwareTags = softwareTags; + this.IntermediateFolder = intermediateFolder; + } + + private string IntermediateFolder { get; } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + string productName = null; + string productVersion = null; + string manufacturer = null; + string upgradeCode = null; + + var summaryInfo = this.Section.Symbols.OfType().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); + var packageCode = NormalizeGuid(summaryInfo?.Value); + + foreach (var property in this.Section.Symbols.OfType()) + { + switch (property.Id.Id) + { + case "ProductName": + productName = property.Value; + break; + case "ProductVersion": + productVersion = property.Value; + break; + case "Manufacturer": + manufacturer = property.Value; + break; + case "UpgradeCode": + upgradeCode = NormalizeGuid(property.Value); + break; + } + } + + var fileSymbolsById = this.Section.Symbols.OfType().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); + + var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); + + Directory.CreateDirectory(workingFolder); + + foreach (var tagRow in this.SoftwareTags) + { + if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) + { + var uniqueId = String.Concat("msi:package/", packageCode); + var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); + + // Write the tag file. + fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; + + using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) + { + CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); + } + + // Ensure the matching "SoftwareIdentificationTag" row exists and + // is populated correctly. + this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) + { + FileRef = fileSymbol.Id.Id, + Regid = tagRow.Regid, + TagId = uniqueId, + PersistentId = persistentId + }); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs new file mode 100644 index 00000000..217609be --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs @@ -0,0 +1,101 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class ProcessPropertiesCommand + { + public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper) + { + this.Section = section; + this.PackageSymbol = packageSymbol; + this.FallbackLcid = fallbackLcid; + this.PopulateDelayedVariables = populateDelayedVariables; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private WixPackageSymbol PackageSymbol { get; } + + private int FallbackLcid { get; } + + private bool PopulateDelayedVariables { get; } + + private IBackendHelper BackendHelper { get; } + + public Dictionary DelayedVariablesCache { get; private set; } + + public string ProductLanguage { get; private set; } + + public void Execute() + { + PropertySymbol languageSymbol = null; + var variableCache = this.PopulateDelayedVariables ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null; + + if (SectionType.Product == this.Section.Type || variableCache != null) + { + foreach (var propertySymbol in this.Section.Symbols.OfType()) + { + // Set the ProductCode if it is to be generated. + if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) + { + propertySymbol.Value = this.BackendHelper.CreateGuid(); + +#if TODO_PATCHING // Is this still necessary? + // Update the target ProductCode in any instance transforms. + foreach (SubStorage subStorage in this.Output.SubStorages) + { + Output subStorageOutput = subStorage.Data; + if (OutputType.Transform != subStorageOutput.Type) + { + continue; + } + + Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; + foreach (Row row in instanceSummaryInformationTable.Rows) + { + if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) + { + row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); + break; + } + } + } +#endif + } + else if ("ProductLanguage" == propertySymbol.Id.Id) + { + languageSymbol = propertySymbol; + } + + // Add the property name and value to the variableCache. + if (variableCache != null) + { + variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value; + } + } + + if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value)) + { + if (languageSymbol == null) + { + languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage"))); + } + + this.PackageSymbol.Language = this.FallbackLcid.ToString(); + languageSymbol.Value = this.FallbackLcid.ToString(); + } + } + + this.DelayedVariablesCache = variableCache; + this.ProductLanguage = languageSymbol?.Value; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..039ba495 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -0,0 +1,125 @@ +// 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.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Defines the file transfers necessary to layout the uncompressed files. + /// + internal class ProcessUncompressedFilesCommand + { + public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) + { + this.Section = section; + this.BackendHelper = backendHelper; + this.PathResolver = pathResolver; + } + + private IntermediateSection Section { get; } + + public IBackendHelper BackendHelper { get; } + + public IPathResolver PathResolver { get; } + + public string DatabasePath { private get; set; } + + public IEnumerable FileFacades { 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 IEnumerable FileTransfers { get; private set; } + + public IEnumerable TrackedFiles { get; private set; } + + public void Execute() + { + var fileTransfers = new List(); + + var trackedFiles = new List(); + + var directories = new Dictionary(); + + var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); + + using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) + { + using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + { + foreach (var directoryRecord in directoryView.Records) + { + var sourceName = this.BackendHelper.GetMsiFileName(directoryRecord.GetString(3), true, this.LongNamesInImage); + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); + + directories.Add(directoryRecord.GetString(1), resolvedDirectory); + } + } + + using (var fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) + { + using (var fileQueryRecord = new Record(1)) + { + // for each file in the array of uncompressed files + foreach (var facade in this.FileFacades) + { + var mediaSymbol = mediaRows[facade.DiskId]; + string relativeFileLayoutPath = null; + var mediaLayoutFolder = mediaSymbol.Layout; + + var mediaLayoutDirectory = this.ResolveMedia(mediaSymbol, mediaLayoutFolder, this.LayoutDirectory); + + // setup up the query record and find the appropriate file in the + // previously executed file view + fileQueryRecord[1] = facade.Id; + fileView.Execute(fileQueryRecord); + + using (var fileRecord = fileView.Fetch()) + { + if (null == fileRecord) + { + throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id)); + } + + relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); + } + + // finally put together the base media layout path and the relative file layout path + var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); + + var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber); + fileTransfers.Add(transfer); + + // Track the location where the cabinet will be placed. If the transfer is + // redundant then then the file should not be cleaned. This is important + // because if the source and destination of the transfer is the same, we + // don't want to clean the file because we'd be deleting the original + // (and that would be bad). + var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber); + tracked.Clean = !transfer.Redundant; + + trackedFiles.Add(tracked); + } + } + } + } + + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs new file mode 100644 index 00000000..94fa0a6a --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -0,0 +1,714 @@ +// 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.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + /// + /// Set sequence numbers for all the actions and create symbols in the output object. + /// + internal class SequenceActionsCommand + { + public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + + this.RelativeActionsForActions = new Dictionary(); + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private Dictionary RelativeActionsForActions { get; } + + public void Execute() + { + var requiredActionSymbols = new Dictionary(); + + // Index all the action symbols and look for collisions. + foreach (var actionSymbol in this.Section.Symbols.OfType()) + { + if (actionSymbol.Overridable) // overridable action + { + if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) + { + if (collidingActionSymbol.Overridable) + { + this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + if (null != collidingActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); + } + } + } + else + { + requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); + } + } + else // unsequenced or sequenced action. + { + // Unsequenced action (allowed for certain standard actions). + if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue) + { + if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction)) + { + // Populate the sequence from the standard action + actionSymbol.Sequence = standardAction.Sequence; + } + else // not a supported unscheduled action. + { + throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); + } + } + + if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) + { + this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + if (null != collidingActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers)); + } + } + else + { + requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; + } + } + } + + // Get the standard actions required based on symbols in the section. + var requiredStandardActions = this.GetRequiredStandardActions(); + + // Add the overridable action symbols that are not overridden to the required action symbols. + foreach (var actionSymbol in requiredStandardActions.Values) + { + if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) + { + requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); + } + } + + // Suppress the required actions that are overridable. + foreach (var suppressActionSymbol in this.Section.Symbols.OfType()) + { + var key = suppressActionSymbol.Id.Id; + + // If there is an overridable symbol to suppress; suppress it. There is no warning if there + // is no action to suppress because the action may be suppressed from a merge module in + // the binder. + if (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol)) + { + if (requiredActionSymbol.Overridable) + { + this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString())); + if (null != requiredActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers)); + } + + requiredActionSymbols.Remove(key); + } + else // suppressing a non-overridable action symbol + { + this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action)); + if (null != requiredActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers)); + } + } + } + } + + // A dictionary used for detecting cyclic references among action symbols. + var firstReference = new Dictionary(); + + // Build up dependency trees of the relatively scheduled actions. + // Use ToList() to create a copy of the required action symbols so that new symbols can + // be added while enumerating. + foreach (var actionSymbol in requiredActionSymbols.Values.ToList()) + { + if (!actionSymbol.Sequence.HasValue) + { + // check for standard actions that don't have a sequence number in a merge module + if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) + { + this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + + this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); + } + else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number + { + this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + } + + // Look for standard actions with sequence restrictions that aren't necessarily scheduled based + // on the presence of a particular table. + if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles")) + { + WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction); + requiredActionSymbols.Add(standardAction.Id.Id, standardAction); + } + + // Schedule actions. + List scheduledActionSymbols; + if (SectionType.Module == this.Section.Type) + { + scheduledActionSymbols = requiredActionSymbols.Values.ToList(); + } + else + { + scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols); + } + + // Remove all existing WixActionSymbols from the section then add the + // scheduled actions back to the section. + var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList(); + + foreach (var removeSymbol in removeActionSymbols) + { + this.Section.RemoveSymbol(removeSymbol); + } + + foreach (var action in scheduledActionSymbols) + { + this.Section.AddSymbol(action); + } + } + + private Dictionary GetRequiredStandardActions() + { + var overridableActionSymbols = new Dictionary(); + + var requiredActionIds = this.GetRequiredActionIds(); + + foreach (var actionId in requiredActionIds) + { + WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction); + overridableActionSymbols.Add(standardAction.Id.Id, standardAction); + } + + return overridableActionSymbols; + } + + private List ScheduleActions(Dictionary requiredActionSymbols) + { + var scheduledActionSymbols = new List(); + + // Process each sequence table individually. + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + // Create a collection of just the action symbols in this sequence + var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList(); + + // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers). + var absoluteActionSymbols = new List(); + foreach (var actionSymbol in sequenceActionSymbols) + { + if (actionSymbol.Sequence.HasValue) + { + // Look for sequence number collisions + foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols) + { + if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence) + { + this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0)); + if (null != sequenceScheduledActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers)); + } + } + } + + absoluteActionSymbols.Add(actionSymbol); + } + } + + absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0)); + + // Schedule the relatively scheduled actions (by resolving the dependency trees). + var previousUsedSequence = 0; + var relativeActionSymbols = new List(); + for (int j = 0; j < absoluteActionSymbols.Count; j++) + { + var absoluteActionSymbol = absoluteActionSymbols[j]; + + // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol. + var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol); + + // Check for relatively scheduled actions occuring before/after a special action + // (those actions with a negative sequence number). + if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any())) + { + // Create errors for all the before actions. + foreach (var actionSymbol in relativeActions.PreviousActions) + { + this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); + } + + // Create errors for all the after actions. + foreach (var actionSymbol in relativeActions.NextActions) + { + this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); + } + + // If there is source line information for the absolutely scheduled action display it + if (absoluteActionSymbol.SourceLineNumbers != null) + { + this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers)); + } + + continue; + } + + // Schedule the action symbols before this one. + var unusedSequence = absoluteActionSymbol.Sequence - 1; + for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--) + { + var relativeActionSymbol = relativeActions.PreviousActions[i]; + + // look for collisions + if (unusedSequence == previousUsedSequence) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); + if (absoluteActionSymbol.SourceLineNumbers != null) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); + } + + unusedSequence++; + } + + relativeActionSymbol.Sequence = unusedSequence; + relativeActionSymbols.Add(relativeActionSymbol); + + unusedSequence--; + } + + // Determine the next used action sequence number. + var nextUsedSequence = Int16.MaxValue + 1; + if (absoluteActionSymbols.Count > j + 1) + { + nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0; + } + + // Schedule the action symbols after this one. + unusedSequence = absoluteActionSymbol.Sequence + 1; + for (var i = 0; i < relativeActions.NextActions.Count; i++) + { + var relativeActionSymbol = relativeActions.NextActions[i]; + + if (unusedSequence == nextUsedSequence) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); + if (absoluteActionSymbol.SourceLineNumbers != null) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); + } + + unusedSequence--; + } + + relativeActionSymbol.Sequence = unusedSequence; + relativeActionSymbols.Add(relativeActionSymbol); + + unusedSequence++; + } + + // keep track of this sequence number as the previous used sequence number for the next iteration + previousUsedSequence = absoluteActionSymbol.Sequence ?? 0; + } + + // add the absolutely and relatively scheduled actions to the list of scheduled actions + scheduledActionSymbols.AddRange(absoluteActionSymbols); + scheduledActionSymbols.AddRange(relativeActionSymbols); + } + + return scheduledActionSymbols; + } + + private IEnumerable GetRequiredActionIds() + { + var set = new HashSet(); + + // gather the required actions for the output type + if (SectionType.Product == this.Section.Type) + { + // AdminExecuteSequence table + set.Add("AdminExecuteSequence/CostFinalize"); + set.Add("AdminExecuteSequence/CostInitialize"); + set.Add("AdminExecuteSequence/FileCost"); + set.Add("AdminExecuteSequence/InstallAdminPackage"); + set.Add("AdminExecuteSequence/InstallFiles"); + set.Add("AdminExecuteSequence/InstallFinalize"); + set.Add("AdminExecuteSequence/InstallInitialize"); + set.Add("AdminExecuteSequence/InstallValidate"); + + // AdminUISequence table + set.Add("AdminUISequence/CostFinalize"); + set.Add("AdminUISequence/CostInitialize"); + set.Add("AdminUISequence/ExecuteAction"); + set.Add("AdminUISequence/FileCost"); + + // AdvtExecuteSequence table + set.Add("AdvertiseExecuteSequence/CostFinalize"); + set.Add("AdvertiseExecuteSequence/CostInitialize"); + set.Add("AdvertiseExecuteSequence/InstallInitialize"); + set.Add("AdvertiseExecuteSequence/InstallFinalize"); + set.Add("AdvertiseExecuteSequence/InstallValidate"); + set.Add("AdvertiseExecuteSequence/PublishFeatures"); + set.Add("AdvertiseExecuteSequence/PublishProduct"); + + // InstallExecuteSequence table + set.Add("InstallExecuteSequence/CostFinalize"); + set.Add("InstallExecuteSequence/CostInitialize"); + set.Add("InstallExecuteSequence/FileCost"); + set.Add("InstallExecuteSequence/InstallFinalize"); + set.Add("InstallExecuteSequence/InstallInitialize"); + set.Add("InstallExecuteSequence/InstallValidate"); + set.Add("InstallExecuteSequence/ProcessComponents"); + set.Add("InstallExecuteSequence/PublishFeatures"); + set.Add("InstallExecuteSequence/PublishProduct"); + set.Add("InstallExecuteSequence/RegisterProduct"); + set.Add("InstallExecuteSequence/RegisterUser"); + set.Add("InstallExecuteSequence/UnpublishFeatures"); + set.Add("InstallExecuteSequence/ValidateProductID"); + + // InstallUISequence table + set.Add("InstallUISequence/CostFinalize"); + set.Add("InstallUISequence/CostInitialize"); + set.Add("InstallUISequence/ExecuteAction"); + set.Add("InstallUISequence/FileCost"); + set.Add("InstallUISequence/ValidateProductID"); + } + + // Gather the required actions for each symbol type. + foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct()) + { + switch (symbolType) + { + case SymbolDefinitionType.AppSearch: + set.Add("InstallExecuteSequence/AppSearch"); + set.Add("InstallUISequence/AppSearch"); + break; + case SymbolDefinitionType.CCPSearch: + set.Add("InstallExecuteSequence/AppSearch"); + set.Add("InstallExecuteSequence/CCPSearch"); + set.Add("InstallExecuteSequence/RMCCPSearch"); + set.Add("InstallUISequence/AppSearch"); + set.Add("InstallUISequence/CCPSearch"); + set.Add("InstallUISequence/RMCCPSearch"); + break; + case SymbolDefinitionType.Class: + set.Add("AdvertiseExecuteSequence/RegisterClassInfo"); + set.Add("InstallExecuteSequence/RegisterClassInfo"); + set.Add("InstallExecuteSequence/UnregisterClassInfo"); + break; + case SymbolDefinitionType.Complus: + set.Add("InstallExecuteSequence/RegisterComPlus"); + set.Add("InstallExecuteSequence/UnregisterComPlus"); + break; + case SymbolDefinitionType.Component: + case SymbolDefinitionType.CreateFolder: + set.Add("InstallExecuteSequence/CreateFolders"); + set.Add("InstallExecuteSequence/RemoveFolders"); + break; + case SymbolDefinitionType.DuplicateFile: + set.Add("InstallExecuteSequence/DuplicateFiles"); + set.Add("InstallExecuteSequence/RemoveDuplicateFiles"); + break; + case SymbolDefinitionType.Environment: + set.Add("InstallExecuteSequence/WriteEnvironmentStrings"); + set.Add("InstallExecuteSequence/RemoveEnvironmentStrings"); + break; + case SymbolDefinitionType.Extension: + set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo"); + set.Add("InstallExecuteSequence/RegisterExtensionInfo"); + set.Add("InstallExecuteSequence/UnregisterExtensionInfo"); + break; + case SymbolDefinitionType.File: + set.Add("InstallExecuteSequence/InstallFiles"); + set.Add("InstallExecuteSequence/RemoveFiles"); + + var foundFont = false; + var foundSelfReg = false; + var foundBindPath = false; + foreach (var file in this.Section.Symbols.OfType()) + { + if (!foundFont && !String.IsNullOrEmpty(file.FontTitle)) + { + set.Add("InstallExecuteSequence/RegisterFonts"); + set.Add("InstallExecuteSequence/UnregisterFonts"); + foundFont = true; + } + + if (!foundSelfReg && file.SelfRegCost.HasValue) + { + set.Add("InstallExecuteSequence/SelfRegModules"); + set.Add("InstallExecuteSequence/SelfUnregModules"); + foundSelfReg = true; + } + + if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath)) + { + set.Add("InstallExecuteSequence/BindImage"); + foundBindPath = true; + } + } + break; + case SymbolDefinitionType.IniFile: + set.Add("InstallExecuteSequence/WriteIniValues"); + set.Add("InstallExecuteSequence/RemoveIniValues"); + break; + case SymbolDefinitionType.IsolatedComponent: + set.Add("InstallExecuteSequence/IsolateComponents"); + break; + case SymbolDefinitionType.LaunchCondition: + set.Add("InstallExecuteSequence/LaunchConditions"); + set.Add("InstallUISequence/LaunchConditions"); + break; + case SymbolDefinitionType.MIME: + set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo"); + set.Add("InstallExecuteSequence/RegisterMIMEInfo"); + set.Add("InstallExecuteSequence/UnregisterMIMEInfo"); + break; + case SymbolDefinitionType.MoveFile: + set.Add("InstallExecuteSequence/MoveFiles"); + break; + case SymbolDefinitionType.Assembly: + set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies"); + set.Add("InstallExecuteSequence/MsiPublishAssemblies"); + set.Add("InstallExecuteSequence/MsiUnpublishAssemblies"); + break; + case SymbolDefinitionType.MsiServiceConfig: + case SymbolDefinitionType.MsiServiceConfigFailureActions: + set.Add("InstallExecuteSequence/MsiConfigureServices"); + break; + case SymbolDefinitionType.ODBCDataSource: + case SymbolDefinitionType.ODBCTranslator: + case SymbolDefinitionType.ODBCDriver: + set.Add("InstallExecuteSequence/SetODBCFolders"); + set.Add("InstallExecuteSequence/InstallODBC"); + set.Add("InstallExecuteSequence/RemoveODBC"); + break; + case SymbolDefinitionType.ProgId: + set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo"); + set.Add("InstallExecuteSequence/RegisterProgIdInfo"); + set.Add("InstallExecuteSequence/UnregisterProgIdInfo"); + break; + case SymbolDefinitionType.PublishComponent: + set.Add("AdvertiseExecuteSequence/PublishComponents"); + set.Add("InstallExecuteSequence/PublishComponents"); + set.Add("InstallExecuteSequence/UnpublishComponents"); + break; + case SymbolDefinitionType.Registry: + case SymbolDefinitionType.RemoveRegistry: + set.Add("InstallExecuteSequence/WriteRegistryValues"); + set.Add("InstallExecuteSequence/RemoveRegistryValues"); + break; + case SymbolDefinitionType.RemoveFile: + set.Add("InstallExecuteSequence/RemoveFiles"); + break; + case SymbolDefinitionType.ServiceControl: + set.Add("InstallExecuteSequence/StartServices"); + set.Add("InstallExecuteSequence/StopServices"); + set.Add("InstallExecuteSequence/DeleteServices"); + break; + case SymbolDefinitionType.ServiceInstall: + set.Add("InstallExecuteSequence/InstallServices"); + break; + case SymbolDefinitionType.Shortcut: + set.Add("AdvertiseExecuteSequence/CreateShortcuts"); + set.Add("InstallExecuteSequence/CreateShortcuts"); + set.Add("InstallExecuteSequence/RemoveShortcuts"); + break; + case SymbolDefinitionType.TypeLib: + set.Add("InstallExecuteSequence/RegisterTypeLibraries"); + set.Add("InstallExecuteSequence/UnregisterTypeLibraries"); + break; + case SymbolDefinitionType.Upgrade: + set.Add("InstallExecuteSequence/FindRelatedProducts"); + set.Add("InstallUISequence/FindRelatedProducts"); + + // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on + // at least one UpgradeVersion element. + if (this.Section.Symbols.OfType().Any(t => t.MigrateFeatures)) + { + set.Add("InstallExecuteSequence/MigrateFeatureStates"); + set.Add("InstallUISequence/MigrateFeatureStates"); + } + break; + } + } + + return set; + } + + /// + /// Sequence an action before or after a standard action. + /// + /// The action symbol to be sequenced. + /// Collection of actions which must be included. + /// A dictionary used for detecting cyclic references among action symbols. + private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) + { + var after = false; + + if (actionSymbol.After != null) + { + after = true; + } + else if (actionSymbol.Before == null) + { + throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); + } + + var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); + var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName; + + if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) + { + // If the missing parent action is a standard action (with a suggested sequence number), add it. + if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol)) + { + // Create a clone to avoid modifying the static copy of the object. + // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone(); + + requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol); + } + else + { + throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this."); + } + } + + this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); + + // Add this action to the appropriate list of dependent action symbols. + var relativeActions = this.GetRelativeActions(parentActionSymbol); + var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions); + relatedSymbols.Add(actionSymbol); + } + + /// + /// Check the specified action symbol to see if it leads to a cycle. + /// + /// Use the provided dictionary to note the initial action symbol that first led to each action + /// symbol. Any action symbol encountered that has already been encountered starting from a different + /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is + /// also not part of a cycle. However, any action symbol encountered that has already been encountered + /// starting from the same initial action symbol is an indication that the current action symbol is + /// part of a cycle. + /// + /// The action symbol to be checked. + /// Collection of actions which must be included. + /// The first encountered action symbol that led to each action symbol. + private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) + { + WixActionSymbol currentActionSymbol = null; + var parentActionSymbol = actionSymbol; + + do + { + var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; + currentActionSymbol = parentActionSymbol; + + if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) + { + firstReference[currentActionSymbol] = actionSymbol; + } + else if (existingInitialActionSymbol == actionSymbol) + { + this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); + } + + parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); + } while (null != parentActionSymbol && !this.Messaging.EncounteredError); + } + + /// + /// Get the action symbol that is the parent of the given action symbol. + /// + /// The given action symbol. + /// Collection of actions which must be included. + /// Null if there is no parent. Used for loop termination. + private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) + { + if (null == actionSymbol.Before && null == actionSymbol.After) + { + return null; + } + + var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); + + if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) + { + WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); + } + + return parentActionSymbol; + } + + + private RelativeActions GetRelativeActions(WixActionSymbol action) + { + if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) + { + relativeActions = new RelativeActions(); + this.RelativeActionsForActions.Add(action.Id.Id, relativeActions); + } + + return relativeActions; + } + + private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action) + { + var relativeActions = new RelativeActions(); + + if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) + { + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions); + + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions); + } + + return relativeActions; + } + + private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List actions, List visitedActions) + { + foreach (var action in actions.Where(a => a.SequenceTable == sequenceType)) + { + if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) + { + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions); + } + + visitedActions.Add(action); + + if (actionRelatives != null) + { + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions); + } + } + } + + private class RelativeActions + { + public List PreviousActions { get; } = new List(); + + public List NextActions { get; } = new List(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..0f77abfc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -0,0 +1,365 @@ +// 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.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Update file information. + /// + internal class UpdateFileFacadesCommand + { + public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable fileFacades, IEnumerable updateFileFacades, IDictionary variableCache, bool overwriteHash) + { + this.Messaging = messaging; + this.Section = section; + this.FileFacades = fileFacades; + this.UpdateFileFacades = updateFileFacades; + this.VariableCache = variableCache; + this.OverwriteHash = overwriteHash; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IEnumerable FileFacades { get; } + + private IEnumerable UpdateFileFacades { get; } + + private bool OverwriteHash { get; } + + private IDictionary VariableCache { get; } + + public void Execute() + { + var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) + { + this.UpdateFileFacade(file, assemblyNameSymbols); + } + } + + private void UpdateFileFacade(IFileFacade facade, Dictionary assemblyNameSymbols) + { + FileInfo fileInfo = null; + try + { + fileInfo = new FileInfo(facade.SourcePath); + } + catch (ArgumentException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); + return; + } + catch (PathTooLongException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); + return; + } + catch (NotSupportedException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); + return; + } + + if (!fileInfo.Exists) + { + this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); + return; + } + + using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (Int32.MaxValue < fileStream.Length) + { + throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); + } + + facade.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(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); + } + else + { + throw new WixException(ErrorMessages.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 != facade.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 => facade.Version.Equals(r.Id, StringComparison.Ordinal))) + { + this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); + } + } + else + { + if (null != facade.Language) + { + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); + } + + int[] hash; + try + { + Installer.GetFileHash(fileInfo.FullName, 0, out hash); + } + catch (Win32Exception e) + { + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND + { + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); + } + else + { + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); + } + } + + if (null == facade.Hash) + { + facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); + } + + facade.Hash.Options = 0; + facade.Hash.HashPart1 = hash[0]; + facade.Hash.HashPart2 = hash[1]; + facade.Hash.HashPart3 = hash[2]; + facade.Hash.HashPart4 = hash[3]; + } + } + 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(facade.Version)) + { + facade.Version = version; + } + else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. + { + // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching + // the version value). We didn't find it so, we will override the default version they provided with the actual + // 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. + facade.Version = version; + } + + if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) + { + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); + } + else // override the default provided by the user (usually nothing) with the actual language from the file itself. + { + facade.Language = language; + } + + // Populate the binder variables for this file information if requested. + if (null != this.VariableCache) + { + if (!String.IsNullOrEmpty(facade.Version)) + { + var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); + this.VariableCache[key] = facade.Version; + } + + if (!String.IsNullOrEmpty(facade.Language)) + { + var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); + this.VariableCache[key] = facade.Language; + } + } + } + + // If this is a CLR assembly, load the assembly and get the assembly name information + if (AssemblyType.DotNetAssembly == facade.AssemblyType) + { + try + { + var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); + + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); + + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); + } + // TODO: WiX v3 seemed to do this but not clear it should actually be done. + //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) + //{ + // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); + //} + + if (assemblyName.StrongNamedSigned) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); + } + else if (facade.AssemblyApplicationFileRef == null) + { + throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); + } + + if (!String.IsNullOrEmpty(assemblyName.FileVersion)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); + } + + // add the assembly name to the information cache + if (null != this.VariableCache) + { + this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + else if (AssemblyType.Win32Assembly == facade.AssemblyType) + { + // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through + // all files like this. Even though this is a rare case it looks like we might be able to index the + // file earlier. + var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); + if (null == fileManifest) + { + this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); + } + + try + { + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); + + if (!String.IsNullOrEmpty(assemblyName.Name)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); + } + + if (!String.IsNullOrEmpty(assemblyName.Version)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); + } + + if (!String.IsNullOrEmpty(assemblyName.Type)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); + } + + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); + } + + if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + } + + /// + /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise + /// create a new row. + /// + /// MsiAssemblyName table. + /// FileFacade containing the assembly read for the MsiAssemblyName row. + /// MsiAssemblyName name. + /// MsiAssemblyName value. + private void SetMsiAssemblyName(Dictionary assemblyNameSymbols, IFileFacade facade, string name, string value) + { + // check for null value (this can occur when grabbing the file version from an assembly without one) + if (String.IsNullOrEmpty(value)) + { + this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); + } + else + { + // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. + if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && + String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && + !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); + } + + // override directly authored value + var lookup = String.Concat(facade.ComponentRef, "/", name); + if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) + { + assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) + { + ComponentRef = facade.ComponentRef, + Name = name, + Value = value, + }); + + if (null == facade.AssemblyNames) + { + facade.AssemblyNames = new List(); + } + + facade.AssemblyNames.Add(assemblyNameSymbol); + + assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); + } + + assemblyNameSymbol.Value = value; + + if (this.VariableCache != null) + { + var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); + this.VariableCache[key] = value; + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs new file mode 100644 index 00000000..66a648cc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs @@ -0,0 +1,77 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class UpdateFromTextFilesCommand + { + public UpdateFromTextFilesCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public void Execute() + { + foreach (var bbControl in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) + { + bbControl.Text = this.ReadTextFile(bbControl.SourceLineNumbers, bbControl.SourceFile.Path); + } + + foreach (var control in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) + { + control.Text = this.ReadTextFile(control.SourceLineNumbers, control.SourceFile.Path); + } + + foreach (var customAction in this.Section.Symbols.OfType().Where(c => c.ScriptFile != null)) + { + customAction.Target = this.ReadTextFile(customAction.SourceLineNumbers, customAction.ScriptFile.Path); + } + } + + /// + /// 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) + { + try + { + using (var reader = new StreamReader(source)) + { + return reader.ReadToEnd(); + } + } + catch (DirectoryNotFoundException e) + { + this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (FileNotFoundException e) + { + this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (IOException e) + { + this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (NotSupportedException) + { + this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, source)); + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs new file mode 100644 index 00000000..affec09f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + + internal class UpdateMediaSequencesCommand + { + public UpdateMediaSequencesCommand(IntermediateSection section, IEnumerable fileFacades) + { + this.Section = section; + this.FileFacades = fileFacades; + } + + private IntermediateSection Section { get; } + + private IEnumerable FileFacades { get; } + + public void Execute() + { + var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); + + // Calculate sequence numbers and media disk id layout for all file media information objects. + if (SectionType.Module == this.Section.Type) + { + var lastSequence = 0; + + foreach (var facade in this.FileFacades) + { + facade.Sequence = ++lastSequence; + } + } + else + { + var lastSequence = 0; + MediaSymbol mediaSymbol = null; + var patchGroups = new Dictionary>(); + + // Sequence the non-patch-added files. + foreach (var facade in this.FileFacades) + { + if (null == mediaSymbol) + { + mediaSymbol = mediaRows[facade.DiskId]; + if (SectionType.Patch == this.Section.Type) + { + // patch Media cannot start at zero + lastSequence = mediaSymbol.LastSequence ?? 1; + } + } + else if (mediaSymbol.DiskId != facade.DiskId) + { + mediaSymbol.LastSequence = lastSequence; + mediaSymbol = mediaRows[facade.DiskId]; + } + + if (facade.PatchGroup.HasValue) + { + if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup)) + { + patchGroup = new List(); + patchGroups.Add(facade.PatchGroup.Value, patchGroup); + } + + patchGroup.Add(facade); + } + else if (!facade.FromModule) + { + facade.Sequence = ++lastSequence; + } + } + + if (null != mediaSymbol) + { + mediaSymbol.LastSequence = lastSequence; + mediaSymbol = null; + } + + // Sequence the patch-added files. + foreach (var patchGroup in patchGroups.Values) + { + foreach (var facade in patchGroup) + { + if (null == mediaSymbol) + { + mediaSymbol = mediaRows[facade.DiskId]; + } + else if (mediaSymbol.DiskId != facade.DiskId) + { + mediaSymbol.LastSequence = lastSequence; + mediaSymbol = mediaRows[facade.DiskId]; + } + + facade.Sequence = ++lastSequence; + } + } + + if (null != mediaSymbol) + { + mediaSymbol.LastSequence = lastSequence; + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs new file mode 100644 index 00000000..981fa0a4 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs @@ -0,0 +1,451 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class UpdateTransformsWithFileFacades + { + public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) + { + this.Messaging = messaging; + this.Output = output; + this.SubStorages = subStorages; + this.TableDefinitions = tableDefinitions; + this.FileFacades = fileFacades; + } + + private IMessaging Messaging { get; } + + private WindowsInstallerData Output { get; } + + private IEnumerable SubStorages { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private IEnumerable FileFacades { get; } + + public void Execute() + { + var fileFacadesByDiskId = new Dictionary>(); + + // Index patch file facades by diskId+fileId. + foreach (var facade in this.FileFacades) + { + if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); + } + + mediaFacades.Add(facade.Id, facade); + } + + var patchMediaRows = new RowDictionary(this.Output.Tables["Media"]); + + // Index paired transforms by name without the "#" prefix. + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); + + // Copy File bind data into substorages + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) + { + var mainTransform = substorage.Data; + + var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); + + var pairedTransform = pairedTransforms["#" + substorage.Name]; + + // Copy Media.LastSequence. + var pairedMediaTable = pairedTransform.Tables["Media"]; + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) + { + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); + pairedMediaRow.LastSequence = patchMediaRow.LastSequence; + } + + // Validate file row changes for keypath-related issues + this.ValidateFileRowChanges(mainTransform); + + // Index File table of pairedTransform + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + var mainFileTable = mainTransform.Tables["File"]; + if (null != mainFileTable) + { + // 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) + { + continue; + } + + // Index patch files by diskId+fileId + if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); + } + + // copy data from the patch back to the transform + if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) + { + var patchFileRow = facade.GetFileRow(); + var pairedFileRow = pairedFileRows.Get(mainFileRow.File); + + for (var i = 0; i < patchFileRow.Fields.Length; i++) + { + var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; + var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; + + if (1 == i) + { + // File.Component_ changes should not come from the shared file rows + // that contain the file information as each individual transform might + // have different changes (or no changes at all). + } + else if (6 == i) // File.Attributes should not changed for binary deltas + { +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + // File.Attribute should not change for binary deltas + pairedFileRow.Attributes = mainFileRow.Attributes; + mainFileRow.Fields[i].Modified = false; + } +#endif + } + else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform + { + // file sequence is updated in Patch table instead of File table for delta patches +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + pairedFileRow.Fields[i].Modified = false; + } + else +#endif + { + pairedFileRow[i] = patchFileRow[i]; + pairedFileRow.Fields[i].Modified = true; + } + mainFileRow.Fields[i].Modified = false; + } + else if (patchValue != mainValue) + { + mainFileRow[i] = patchFileRow[i]; + mainFileRow.Fields[i].Modified = true; + if (mainFileRow.Operation == RowOperation.None) + { + mainFileRow.Operation = RowOperation.Modify; + } + } + } + + // Copy MsiFileHash row for this File. + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) + { + //patchHashRow = patchFileRow.Hash; + throw new NotImplementedException(); + } + + if (null != patchHashRow) + { + var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchHashRow.Fields.Length; i++) + { + mainHashRow[i] = patchHashRow[i]; + if (i > 1) + { + // assume all hash fields have been modified + mainHashRow.Fields[i].Modified = true; + } + } + + // assume the MsiFileHash operation follows the File one + mainHashRow.Operation = mainFileRow.Operation; + } + + // copy MsiAssemblyName rows for this File +#if TODO_PATCHING + List patchAssemblyNameRows = patchFileRow.AssemblyNames; + if (null != patchAssemblyNameRows) + { + var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + foreach (var patchAssemblyNameRow in patchAssemblyNameRows) + { + // Copy if there isn't an identical modified/added row already in the transform. + var foundMatchingModifiedRow = false; + foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) + { + if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) + { + foundMatchingModifiedRow = true; + break; + } + } + + if (!foundMatchingModifiedRow) + { + var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) + { + mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; + } + + // assume value field has been modified + mainAssemblyNameRow.Fields[2].Modified = true; + mainAssemblyNameRow.Operation = mainFileRow.Operation; + } + } + } +#endif + + // Add patch header for this file +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. + this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); + this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); + + // Add to Patch table + var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); + if (0 == patchTable.Rows.Count) + { + patchTable.Operation = TableOperation.Add; + } + + var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); + patchRow[0] = patchFileRow.File; + patchRow[1] = patchFileRow.Sequence; + + var patchFile = new FileInfo(patchFileRow.Source); + patchRow[2] = (int)patchFile.Length; + patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; + + var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; + if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) + { + streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); + + var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); + if (0 == patchHeadersTable.Rows.Count) + { + patchHeadersTable.Operation = TableOperation.Add; + } + + var 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; + } +#endif + } + else + { + // TODO: throw because all transform rows should have made it into the patch + } + } + } + + this.Output.Tables.Remove("Media"); + this.Output.Tables.Remove("File"); + this.Output.Tables.Remove("MsiFileHash"); + this.Output.Tables.Remove("MsiAssemblyName"); + } + } + + /// + /// 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, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) + { + var tableName = table.ToString(); + + // Find/add PatchFiles action (also determine sequence for it). + // Search mainTransform first, then pairedTransform (pairedTransform overrides). + var hasPatchFilesAction = false; + var installFilesSequence = 0; + var duplicateFilesSequence = 0; + + TestSequenceTableForPatchFilesAction( + mainTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + TestSequenceTableForPatchFilesAction( + pairedTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + if (!hasPatchFilesAction) + { + WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionSymbol); + + var sequence = patchFilesActionSymbol.Sequence; + + // Test for default sequence value's appropriateness + if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) + { + if (0 != duplicateFilesSequence) + { + if (duplicateFilesSequence < installFilesSequence) + { + throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); + } + else + { + sequence = (duplicateFilesSequence + installFilesSequence) / 2; + if (installFilesSequence == sequence || duplicateFilesSequence == sequence) + { + throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); + } + } + } + else + { + sequence = installFilesSequence + 1; + } + } + + var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); + if (0 == sequenceTable.Rows.Count) + { + sequenceTable.Operation = TableOperation.Add; + } + + var patchAction = sequenceTable.CreateRow(null); + patchAction[0] = patchFilesActionSymbol.Action; + patchAction[1] = patchFilesActionSymbol.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 sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) + { + if (null != sequenceTable) + { + foreach (var row in sequenceTable.Rows) + { + var actionName = row.FieldAsString(0); + switch (actionName) + { + case "PatchFiles": + hasPatchFilesAction = true; + break; + + case "InstallFiles": + installFilesSequence = row.FieldAsInteger(2); + break; + + case "DuplicateFiles": + duplicateFilesSequence = row.FieldAsInteger(2); + break; + } + } + } + } + + /// + /// 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(WindowsInstallerData transform) + { + var componentTable = transform.Tables["Component"]; + var 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; + } + + var componentKeyPath = new Dictionary(componentTable.Rows.Count); + + // Index the Component table for non-directory & non-registry key paths. + foreach (var row in componentTable.Rows) + { + var keyPath = row.FieldAsString(5); + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + { + componentKeyPath.Add(row.FieldAsString(0), keyPath); + } + } + + var componentWithChangedKeyPath = new Dictionary(); + var componentWithNonKeyPathChanged = new Dictionary(); + // Verify changes in the file table, now that file diffing has occurred + foreach (FileRow row in fileTable.Rows) + { + if (RowOperation.Modify != row.Operation) + { + continue; + } + + var fileId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + // 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 (var componentFile in componentWithNonKeyPathChanged) + { + // Make sure all changes to non keypath files also had a change in the keypath. + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) + { + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs new file mode 100644 index 00000000..cf1e21c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs @@ -0,0 +1,187 @@ +// 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.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback + { + // Set of ICEs that have equivalent-or-better checks in WiX. + private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; + + public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Data = data; + this.OutputPath = outputPath; + this.CubeFiles = cubeFiles; + this.Ices = ices; + this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); + + this.IntermediateFolder = intermediateFolder; + this.OutputSourceLineNumber = new SourceLineNumber(outputPath); + } + + public IEnumerable TrackedFiles { get; private set; } + + /// + /// Encountered error implementation for . + /// + public bool EncounteredError => this.Messaging.EncounteredError; + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private WindowsInstallerData Data { get; } + + private string OutputPath { get; } + + private IEnumerable CubeFiles { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private string IntermediateFolder { get; } + + /// + /// Fallback when an exact source line number cannot be calculated for a validation error. + /// + private SourceLineNumber OutputSourceLineNumber { get; set; } + + private Dictionary SourceLineNumbersByTablePrimaryKey { get; set; } + + public void Execute() + { + var trackedFiles = new List(); + var stopwatch = Stopwatch.StartNew(); + + this.Messaging.Write(VerboseMessages.ValidatingDatabase()); + + // Ensure the temporary files can be created the working folder. + var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); + Directory.CreateDirectory(workingFolder); + + // Copy the database to a temporary location so it can be manipulated. + // Ensure it is not read-only. + var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); + FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); + + var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary); + trackedFiles.Add(trackWorkingDatabase); + + var attributes = File.GetAttributes(workingDatabasePath); + File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); + + var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); + validator.Execute(); + + stopwatch.Stop(); + this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + + + this.TrackedFiles = trackedFiles; + } + + private void LogValidationMessage(ValidationMessage message) + { + var messageSourceLineNumbers = this.OutputSourceLineNumber; + if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) + { + messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); + } + + switch (message.Type) + { + case ValidationMessageType.InternalFailure: + case ValidationMessageType.Error: + this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Warning: + this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Info: + this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); + break; + default: + throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); + } + } + + /// + /// Validation blocked by other installation operation for . + /// + public void ValidationBlocked() + { + this.Messaging.Write(VerboseMessages.ValidationSerialized()); + } + + /// + /// Validation message implementation for . + /// + public bool ValidationMessage(ValidationMessage message) + { + this.LogValidationMessage(message); + return true; + } + + /// + /// 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. + private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable primaryKeys) + { + // Source line information only exists if an output file was supplied + if (this.Data == null) + { + // Use the file name as the source line information. + return this.OutputSourceLineNumber; + } + + // Index the source line information if it hasn't been indexed already. + if (this.SourceLineNumbersByTablePrimaryKey == null) + { + this.SourceLineNumbersByTablePrimaryKey = new Dictionary(); + + // Index each real table + foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) + { + // Index each row that contain source line information + foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) + { + // Index the row using its table name and primary key + var primaryKey = row.GetPrimaryKey(';'); + + if (!String.IsNullOrEmpty(primaryKey)) + { + try + { + var key = String.Concat(table.Name, ":", primaryKey); + this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); + } + catch (ArgumentException) + { + this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); + } + } + } + } + } + + return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs new file mode 100644 index 00000000..aeda4443 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs @@ -0,0 +1,96 @@ +// 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.Decompile +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileMsiOrMsmCommand + { + public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable backendExtensions) + { + this.Context = context; + this.Extensions = backendExtensions; + this.Messaging = context.ServiceProvider.GetService(); + } + + private IDecompileContext Context { get; } + + private IEnumerable Extensions { get; } + + private IMessaging Messaging { get; } + + public IDecompileResult Execute() + { + var result = this.Context.ServiceProvider.GetService(); + + try + { + using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly)) + { + // Delete the directory and its files to prevent cab extraction failure due to an existing file. + if (Directory.Exists(this.Context.ExtractFolder)) + { + Directory.Delete(this.Context.ExtractFolder, true); + } + + var backendHelper = this.Context.ServiceProvider.GetService(); + + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, this.Context.DecompilePath, this.Context.DecompileType, this.Context.ExtractFolder, this.Context.IntermediateFolder, this.Context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false); + var output = unbindCommand.Execute(); + var extractedFilePaths = new List(unbindCommand.ExportedFiles); + + var decompiler = new Decompiler(this.Messaging, backendHelper, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule); + result.Document = decompiler.Decompile(output); + + result.Platform = GetPlatformFromOutput(output); + + // extract the files from the cabinets + if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets) + { + var fileDirectory = String.IsNullOrEmpty(this.Context.CabinetExtractFolder) ? Path.Combine(this.Context.ExtractFolder, "File") : this.Context.CabinetExtractFolder; + + var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, fileDirectory, this.Context.IntermediateFolder, this.Context.TreatProductAsModule); + extractCommand.Execute(); + + extractedFilePaths.AddRange(extractCommand.ExtractedFiles); + result.ExtractedFilePaths = extractedFilePaths; + } + else + { + result.ExtractedFilePaths = new string[0]; + } + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath)); + } + + throw; + } + + return result; + } + + private static Platform? GetPlatformFromOutput(WindowsInstallerData output) + { + var template = output.Tables["_SummaryInformation"]?.Rows.SingleOrDefault(row => row.FieldAsInteger(0) == 7)?.FieldAsString(1); + + return Decompiler.GetPlatformFromTemplateSummaryInformation(template?.Split(';')); + + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs new file mode 100644 index 00000000..0b45a8b3 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -0,0 +1,7596 @@ +// 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.Decompile +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Decompiles an msi database into WiX source. + /// + internal class Decompiler + { + private static readonly Regex NullSplitter = new Regex(@"\[~]"); + + // NameToBit arrays + private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; + private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; + private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; + private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; + private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; + private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; + private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; + private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; + private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; + private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; + private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; + private XElement uiElement; + + /// + /// Creates a new decompiler object with a default set of table definitions. + /// + public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Extensions = extensions; + this.BaseSourcePath = baseSourcePath ?? "SourceDir"; + this.SuppressCustomTables = suppressCustomTables; + this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; + this.SuppressUI = suppressUI; + this.TreatProductAsModule = treatProductAsModule; + + this.ExtensionsByTableName = new Dictionary(); + this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); + + this.TableDefinitions = new TableDefinitionCollection(); + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IEnumerable Extensions { get; } + + private Dictionary ExtensionsByTableName { get; } + + private string BaseSourcePath { get; } + + private bool SuppressCustomTables { get; } + + private bool SuppressDroppingEmptyTables { get; } + + private bool SuppressRelativeActionSequencing { get; } + + private bool SuppressUI { get; } + + private bool TreatProductAsModule { get; } + + private OutputType OutputType { get; set; } + + private Dictionary StandardActions { get; } + + private bool Compressed { get; set; } + + private XElement RootElement { get; set; } + + private TableDefinitionCollection TableDefinitions { get; } + + private bool ShortNames { get; set; } + + private string ModularizationGuid { get; set; } + + private XElement UIElement + { + get + { + if (null == this.uiElement) + { + this.uiElement = new XElement(Names.UIElement); + this.RootElement.Add(this.uiElement); + } + + return this.uiElement; + } + } + + private Dictionary Singletons { get; } = new Dictionary(); + + private Dictionary IndexedElements { get; } = new Dictionary(); + + private Dictionary PatchTargetFiles { get; } = new Dictionary(); + + /// + /// Decompile the database file. + /// + /// The output to decompile. + /// The serialized WiX source code. + public XDocument Decompile(WindowsInstallerData output) + { + if (null == output) + { + throw new ArgumentNullException(nameof(output)); + } + + this.OutputType = output.Type; + + // collect the table definitions from the output + this.TableDefinitions.Clear(); + foreach (var table in output.Tables) + { + this.TableDefinitions.Add(table.Definition); + } + + // add any missing standard and wix-specific table definitions + foreach (var tableDefinition in WindowsInstallerTableDefinitions.All) + { + if (!this.TableDefinitions.Contains(tableDefinition.Name)) + { + this.TableDefinitions.Add(tableDefinition); + } + } + + // add any missing extension table definitions +#if TODO_DECOMPILER_EXTENSIONS + foreach (var extension in this.Extensions) + { + this.AddExtension(extension); + } +#endif + + switch (this.OutputType) + { + case OutputType.Module: + this.RootElement = new XElement(Names.ModuleElement); + break; + case OutputType.PatchCreation: + this.RootElement = new XElement(Names.PatchCreationElement); + break; + case OutputType.Product: + this.RootElement = new XElement(Names.PackageElement); + break; + default: + throw new InvalidOperationException("Unknown output type."); + } + + var xWix = new XElement(Names.WixElement, this.RootElement); + + // try to decompile the database file + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + this.InitializeDecompile(output.Tables, output.Codepage); + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // decompile the tables + this.DecompileTables(output); + + // finalize the decompiler and its extensions + this.FinalizeDecompile(output.Tables); + + // return the XML document only if decompilation completed successfully + var document = new XDocument(xWix); + return this.Messaging.EncounteredError ? null : document; + } + +#if TODO_DECOMPILER_EXTENSIONS + private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension) + { + if (null != extension.TableDefinitions) + { + foreach (TableDefinition tableDefinition in extension.TableDefinitions) + { + if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name)) + { + this.ExtensionsByTableName.Add(tableDefinition.Name, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); + } + } + } + } +#endif + + internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template) + { + if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length) + { + switch (template[0]) + { + case "Intel": + return Platform.X86; + case "x64": + return Platform.X64; + case "Arm64": + return Platform.ARM64; + } + } + + return null; + } + + /// + /// Gets the element corresponding to the row it came from. + /// + /// The row corresponding to the element. + /// The indexed element. + private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + /// + /// Gets the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The primary key corresponding to the element. + /// The indexed element. + private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; + + /// + /// Tries to get the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The indexed element. + /// Whether the element was found. + private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + /// + /// Tries to get the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The indexed element. + /// The primary key corresponding to the element. + /// Whether the element was found. + private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement); + + /// + /// Index an element by its corresponding row. + /// + /// The row corresponding to the element. + /// The element to index. + private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element) + { + this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); + } + + /// + /// Index an element by its corresponding row. + /// + /// The element to index. + /// + /// + private void IndexElement(XElement element, string table, params string[] primaryKey) + { + this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element); + } + + private Dictionary> IndexTableOneToMany(IEnumerable rows, int column = 0) + { + return rows + .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row)) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + } + + private Dictionary> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty(), column); + + private Dictionary> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty(), column); + + private void AddChildToParent(string parentName, XElement xChild, Row row, int column) + { + var key = row.FieldAsString(column); + if (this.TryGetIndexedElement(parentName, out var xParent, key)) + { + xParent.Add(xChild); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName)); + } + } + + private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column)); + + private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value) + { + if (!String.IsNullOrEmpty(value)) + { + xElement.SetAttributeValue(attributeName, value); + } + } + + private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value) + { + if (value.HasValue) + { + xElement.SetAttributeValue(attributeName, value); + } + } + + /// + /// Convert an Int32 into a DateTime. + /// + /// The Int32 value. + /// The DateTime. + private static DateTime ConvertIntegerToDateTime(int value) + { + var date = value / 65536; + var time = value % 65536; + + return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); + } + + /// + /// Set the common control attributes in a control element. + /// + /// The control attributes. + /// The control element. + private static void SetControlAttributes(int attributes, XElement xControl) + { + if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled)) + { + xControl.SetAttributeValue("Disabled", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect)) + { + xControl.SetAttributeValue("Indirect", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger)) + { + xControl.SetAttributeValue("Integer", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll)) + { + xControl.SetAttributeValue("LeftScroll", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned)) + { + xControl.SetAttributeValue("RightAligned", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO)) + { + xControl.SetAttributeValue("RightToLeft", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken)) + { + xControl.SetAttributeValue("Sunken", "yes"); + } + + if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible)) + { + xControl.SetAttributeValue("Hidden", "yes"); + } + } + + /// + /// Creates an action element. + /// + /// The action from which the element should be created. + private void CreateActionElement(WixActionSymbol actionSymbol) + { + XElement xAction; + + if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action + { + xAction = new XElement(Names.CustomElement, + new XAttribute("Action", actionSymbol.Action), + String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition)); + + switch (actionSymbol.Sequence) + { + case (-4): + xAction.SetAttributeValue("OnExit", "suspend"); + break; + case (-3): + xAction.SetAttributeValue("OnExit", "error"); + break; + case (-2): + xAction.SetAttributeValue("OnExit", "cancel"); + break; + case (-1): + xAction.SetAttributeValue("OnExit", "success"); + break; + default: + if (null != actionSymbol.Before) + { + xAction.SetAttributeValue("Before", actionSymbol.Before); + } + else if (null != actionSymbol.After) + { + xAction.SetAttributeValue("After", actionSymbol.After); + } + else if (actionSymbol.Sequence.HasValue) + { + xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); + } + break; + } + } + else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog + { + xAction = new XElement(Names.CustomElement, + new XAttribute("Dialog", actionSymbol.Action), + new XAttribute("Condition", actionSymbol.Condition)); + + switch (actionSymbol.Sequence) + { + case (-4): + xAction.SetAttributeValue("OnExit", "suspend"); + break; + case (-3): + xAction.SetAttributeValue("OnExit", "error"); + break; + case (-2): + xAction.SetAttributeValue("OnExit", "cancel"); + break; + case (-1): + xAction.SetAttributeValue("OnExit", "success"); + break; + default: + SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); + break; + } + } + else // possibly a standard action without suggested sequence information + { + xAction = this.CreateStandardActionElement(actionSymbol); + } + + // add the action element to the appropriate sequence element + if (null != xAction) + { + var sequenceTable = actionSymbol.SequenceTable.ToString(); + if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence)) + { + xSequence = new XElement(Names.WxsNamespace + sequenceTable); + + this.RootElement.Add(xSequence); + this.Singletons.Add(sequenceTable, xSequence); + } + + try + { + xSequence.Add(xAction); + } + catch (ArgumentException) // action/dialog is not valid for this sequence + { + this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + } + } + + /// + /// Creates a standard action element. + /// + /// The action row from which the element should be created. + /// The created element. + private XElement CreateStandardActionElement(WixActionSymbol actionSymbol) + { + XElement xStandardAction = null; + + switch (actionSymbol.Action) + { + case "AllocateRegistrySpace": + case "BindImage": + case "CostFinalize": + case "CostInitialize": + case "CreateFolders": + case "CreateShortcuts": + case "DeleteServices": + case "DuplicateFiles": + case "ExecuteAction": + case "FileCost": + case "InstallAdminPackage": + case "InstallFiles": + case "InstallFinalize": + case "InstallInitialize": + case "InstallODBC": + case "InstallServices": + case "InstallValidate": + case "IsolateComponents": + case "MigrateFeatureStates": + case "MoveFiles": + case "MsiPublishAssemblies": + case "MsiUnpublishAssemblies": + case "PatchFiles": + case "ProcessComponents": + case "PublishComponents": + case "PublishFeatures": + case "PublishProduct": + case "RegisterClassInfo": + case "RegisterComPlus": + case "RegisterExtensionInfo": + case "RegisterFonts": + case "RegisterMIMEInfo": + case "RegisterProduct": + case "RegisterProgIdInfo": + case "RegisterTypeLibraries": + case "RegisterUser": + case "RemoveDuplicateFiles": + case "RemoveEnvironmentStrings": + case "RemoveFiles": + case "RemoveFolders": + case "RemoveIniValues": + case "RemoveODBC": + case "RemoveRegistryValues": + case "RemoveShortcuts": + case "SelfRegModules": + case "SelfUnregModules": + case "SetODBCFolders": + case "StartServices": + case "StopServices": + case "UnpublishComponents": + case "UnpublishFeatures": + case "UnregisterClassInfo": + case "UnregisterComPlus": + case "UnregisterExtensionInfo": + case "UnregisterFonts": + case "UnregisterMIMEInfo": + case "UnregisterProgIdInfo": + case "UnregisterTypeLibraries": + case "ValidateProductID": + case "WriteEnvironmentStrings": + case "WriteIniValues": + case "WriteRegistryValues": + xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); + break; + + case "AppSearch": + this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); + + if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) + { + xStandardAction = new XElement(Names.AppSearchElement); + + SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition); + SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence); + + return xStandardAction; + } + break; + + case "CCPSearch": + case "DisableRollback": + case "FindRelatedProducts": + case "ForceReboot": + case "InstallExecute": + case "InstallExecuteAgain": + case "LaunchConditions": + case "RemoveExistingProducts": + case "ResolveSource": + case "RMCCPSearch": + case "ScheduleReboot": + xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); + Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction); + return xStandardAction; + + default: + this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + return null; + } + + if (xStandardAction != null) + { + this.SequenceStandardAction(actionSymbol, xStandardAction); + } + + return xStandardAction; + } + + /// + /// Applies the condition and sequence to a standard action element based on the action symbol data. + /// + /// Action data from the database. + /// Element to be sequenced. + private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction) + { + xAction.SetAttributeValue("Condition", actionSymbol.Condition); + + if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence) + { + this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + else if (actionSymbol.Sequence.HasValue) + { + xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); + } + } + + /// + /// Applies the condition and relative sequence to an action element based on the action row data. + /// + /// Action data from the database. + /// Element to be sequenced. + private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction) + { + SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition); + SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); + } + + /// + /// Ensure that a particular property exists in the decompiled output. + /// + /// The identifier of the property. + /// The property element. + private XElement EnsureProperty(string id) + { + XElement xProperty; + + if (!this.TryGetIndexedElement("Property", out xProperty, id)) + { + xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id)); + + this.RootElement.Add(xProperty); + this.IndexElement(xProperty, "Property", id); + } + + return xProperty; + } + + /// + /// Finalize decompilation. + /// + /// The collection of all tables. + private void FinalizeDecompile(TableIndexedCollection tables) + { + if (OutputType.PatchCreation == this.OutputType) + { + this.FinalizeFamilyFileRangesTable(tables); + } + else + { + this.FinalizeSummaryInformationStream(tables); + this.FinalizeCheckBoxTable(tables); + this.FinalizeComponentTable(tables); + this.FinalizeDialogTable(tables); + this.FinalizeDuplicateMoveFileTables(tables); + this.FinalizeFeatureComponentsTable(tables); + this.FinalizeFileTable(tables); + this.FinalizeMIMETable(tables); + this.FinalizeMsiLockPermissionsExTable(tables); + this.FinalizeLockPermissionsTable(tables); + this.FinalizeProgIdTable(tables); + this.FinalizePropertyTable(tables); + this.FinalizeRemoveFileTable(tables); + this.FinalizeSearchTables(tables); + this.FinalizeShortcutTable(tables); + this.FinalizeUpgradeTable(tables); + this.FinalizeSequenceTables(tables); + this.FinalizeVerbTable(tables); + } + } + + /// + /// Finalize the CheckBox table. + /// + /// The collection of all tables. + /// + /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with + /// a value in the Property column. This is then possibly matched up with a CheckBox row + /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. + /// + private void FinalizeCheckBoxTable(TableIndexedCollection tables) + { + // if the user has requested to suppress the UI elements, we have nothing to do + if (this.SuppressUI) + { + return; + } + + var checkBoxTable = tables["CheckBox"]; + var controlTable = tables["Control"]; + + var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false); + + // enumerate through the Control table, adding CheckBox values where appropriate + if (null != controlTable) + { + foreach (var row in controlTable.Rows) + { + var xControl = this.GetIndexedElement(row); + + if ("CheckBox" == row.FieldAsString(2)) + { + var property = row.FieldAsString(8); + if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow)) + { + // if we've seen this property already, create a reference to it + if (checkBoxProperties.TryGetValue(property, out var seen) && seen) + { + xControl.SetAttributeValue("CheckBoxPropertyRef", property); + } + else + { + xControl.SetAttributeValue("Property", property); + checkBoxProperties[property] = true; + } + + xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox")); + } + } + } + } + } + + /// + /// Finalize the Component table. + /// + /// The collection of all tables. + /// + /// Set the keypaths for each component. + /// + private void FinalizeComponentTable(TableIndexedCollection tables) + { + var componentTable = tables["Component"]; + var fileTable = tables["File"]; + var odbcDataSourceTable = tables["ODBCDataSource"]; + var registryTable = tables["Registry"]; + + // set the component keypaths + if (null != componentTable) + { + foreach (var row in componentTable.Rows) + { + var attributes = row.FieldAsInteger(3); + var keyPath = row.FieldAsString(5); + + if (String.IsNullOrEmpty(keyPath)) + { + var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0)); + xComponent.SetAttributeValue("KeyPath", "yes"); + } + else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + { + if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath)) + { + if (xRegistry.Name.LocalName == "RegistryValue") + { + xRegistry.SetAttributeValue("KeyPath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath)); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry")); + } + } + else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)) + { + if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath)) + { + xOdbcDataSource.SetAttributeValue("KeyPath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource")); + } + } + else + { + if (this.TryGetIndexedElement("File", out var xFile, keyPath)) + { + xFile.SetAttributeValue("KeyPath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File")); + } + } + } + } + + // add the File children elements + if (null != fileTable) + { + foreach (FileRow fileRow in fileTable.Rows) + { + if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component) + && this.TryGetIndexedElement(fileRow, out var xFile)) + { + xComponent.Add(xFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); + } + } + } + + // add the ODBCDataSource children elements + if (null != odbcDataSourceTable) + { + foreach (var row in odbcDataSourceTable.Rows) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1)) + && this.TryGetIndexedElement(row, out var xOdbcDataSource)) + { + xComponent.Add(xOdbcDataSource); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); + } + } + } + + // add the Registry children elements + if (null != registryTable) + { + foreach (var row in registryTable.Rows) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5)) + && this.TryGetIndexedElement(row, out var xRegistry)) + { + xComponent.Add(xRegistry); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component")); + } + } + } + } + + /// + /// Finalize the Dialog table. + /// + /// The collection of all tables. + /// + /// Sets the first, default, and cancel control for each dialog and adds all child control + /// elements to the dialog. + /// + private void FinalizeDialogTable(TableIndexedCollection tables) + { + // if the user has requested to suppress the UI elements, we have nothing to do + if (this.SuppressUI) + { + return; + } + + var addedControls = new HashSet(); + + var controlTable = tables["Control"]; + var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + var dialogTable = tables["Dialog"]; + if (null != dialogTable) + { + foreach (var dialogRow in dialogTable.Rows) + { + var xDialog = this.GetIndexedElement(dialogRow); + var dialogId = dialogRow.FieldAsString(0); + + if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7))) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control")); + } + + // add tabbable controls + while (null != xControl) + { + var controlId = xControl.Attribute("Id"); + var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)]; + + xControl.SetAttributeValue("TabSkip", "no"); + + xDialog.Add(xControl); + addedControls.Add(xControl); + + var controlNext = controlRow.FieldAsString(10); + if (!String.IsNullOrEmpty(controlNext)) + { + if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext)) + { + // looped back to the first control in the dialog + if (addedControls.Contains(xControl)) + { + xControl = null; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control")); + } + } + else + { + xControl = null; + } + } + + // set default control + var controlDefault = dialogRow.FieldAsString(8); + if (!String.IsNullOrEmpty(controlDefault)) + { + if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault)) + { + xDefaultControl.SetAttributeValue("Default", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control")); + } + } + + // set cancel control + var controlCancel = dialogRow.FieldAsString(8); + if (!String.IsNullOrEmpty(controlCancel)) + { + if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel)) + { + xCancelControl.SetAttributeValue("Cancel", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control")); + } + } + } + } + + // add the non-tabbable controls to the dialog + if (null != controlTable) + { + foreach (var controlRow in controlTable.Rows) + { + var dialogId = controlRow.FieldAsString(0); + if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog")); + continue; + } + + var xControl = this.GetIndexedElement(controlRow); + if (!addedControls.Contains(xControl)) + { + xControl.SetAttributeValue("TabSkip", "yes"); + xDialog.Add(xControl); + } + } + } + } + + /// + /// Finalize the DuplicateFile and MoveFile tables. + /// + /// The collection of all tables. + /// + /// Sets the source/destination property/directory for each DuplicateFile or + /// MoveFile row. + /// + private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) + { + var duplicateFileTable = tables["DuplicateFile"]; + if (null != duplicateFileTable) + { + foreach (var row in duplicateFileTable.Rows) + { + var xCopyFile = this.GetIndexedElement(row); + var destination = row.FieldAsString(4); + if (!String.IsNullOrEmpty(destination)) + { + if (this.TryGetIndexedElement("Directory", out var _, destination)) + { + xCopyFile.SetAttributeValue("DestinationDirectory", destination); + } + else + { + xCopyFile.SetAttributeValue("DestinationProperty", destination); + } + } + } + } + + var moveFileTable = tables["MoveFile"]; + if (null != moveFileTable) + { + foreach (var row in moveFileTable.Rows) + { + var xCopyFile = this.GetIndexedElement(row); + var source = row.FieldAsString(4); + if (!String.IsNullOrEmpty(source)) + { + if (this.TryGetIndexedElement("Directory", out var _, source)) + { + xCopyFile.SetAttributeValue("SourceDirectory", source); + } + else + { + xCopyFile.SetAttributeValue("SourceProperty", source); + } + } + + var destination = row.FieldAsString(5); + if (this.TryGetIndexedElement("Directory", out var _, destination)) + { + xCopyFile.SetAttributeValue("DestinationDirectory", destination); + } + else + { + xCopyFile.SetAttributeValue("DestinationProperty", destination); + } + } + } + } + + /// + /// Finalize the FamilyFileRanges table. + /// + /// The collection of all tables. + private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) + { + var familyFileRangesTable = tables["FamilyFileRanges"]; + if (null != familyFileRangesTable) + { + foreach (var row in familyFileRangesTable.Rows) + { + var xProtectRange = new XElement(Names.ProtectRangeElement); + + if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) + { + var retainOffsets = row.FieldAsString(2).Split(','); + var retainLengths = row.FieldAsString(3).Split(','); + + if (retainOffsets.Length == retainLengths.Length) + { + for (var i = 0; i < retainOffsets.Length; i++) + { + if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16)); + } + else + { + xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture)); + } + + if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16)); + } + else + { + xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture)); + } + } + } + else + { + // TODO: warn + } + } + else if (!row.IsColumnNull(2) || !row.IsColumnNull(3)) + { + // TODO: warn about mismatch between columns + } + + this.IndexElement(row, xProtectRange); + } + } + + var usedProtectRanges = new HashSet(); + var externalFilesTable = tables["ExternalFiles"]; + if (null != externalFilesTable) + { + foreach (var row in externalFilesTable.Rows) + { + if (this.TryGetIndexedElement(row, out var xExternalFile) + && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0))) + { + xExternalFile.Add(xProtectRange); + usedProtectRanges.Add(xProtectRange); + } + } + } + + var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; + if (null != targetFiles_OptionalDataTable) + { + var targetImagesTable = tables["TargetImages"]; + var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); + + var upgradedImagesTable = tables["UpgradedImages"]; + var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); + + foreach (var row in targetFiles_OptionalDataTable.Rows) + { + var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; + + if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); + continue; + } + + if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages")); + continue; + } + + if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1))) + { + xTargetFile.Add(xProtectRange); + usedProtectRanges.Add(xProtectRange); + } + } + } + + if (null != familyFileRangesTable) + { + foreach (var row in familyFileRangesTable.Rows) + { + var xProtectRange = this.GetIndexedElement(row); + + if (!usedProtectRanges.Contains(xProtectRange)) + { + var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1))); + xProtectFile.Add(xProtectRange); + + this.AddChildToParent("ImageFamilies", xProtectFile, row, 0); + } + } + } + } + + /// + /// Finalize the FeatureComponents table. + /// + /// The collection of all tables. + /// + /// Since tables specifying references to the FeatureComponents table have references to + /// the Feature and Component table separately, but not the FeatureComponents table specifically, + /// the FeatureComponents table and primary features must be decompiled during finalization. + /// + private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) + { + var classTable = tables["Class"]; + if (null != classTable) + { + foreach (var row in classTable.Rows) + { + this.SetPrimaryFeature(row, 11, 2); + } + } + + var extensionTable = tables["Extension"]; + if (null != extensionTable) + { + foreach (var row in extensionTable.Rows) + { + this.SetPrimaryFeature(row, 4, 1); + } + } + + var msiAssemblyTable = tables["MsiAssembly"]; + if (null != msiAssemblyTable) + { + foreach (var row in msiAssemblyTable.Rows) + { + this.SetPrimaryFeature(row, 1, 0); + } + } + + var publishComponentTable = tables["PublishComponent"]; + if (null != publishComponentTable) + { + foreach (var row in publishComponentTable.Rows) + { + this.SetPrimaryFeature(row, 4, 2); + } + } + + var typeLibTable = tables["TypeLib"]; + if (null != typeLibTable) + { + foreach (var row in typeLibTable.Rows) + { + this.SetPrimaryFeature(row, 6, 2); + } + } + } + + /// + /// Finalize the File table. + /// + /// The collection of all tables. + /// + /// Sets the source, diskId, and assembly information for each file. + /// + private void FinalizeFileTable(TableIndexedCollection tables) + { + // index the media table by media id + var mediaTable = tables["Media"]; + var mediaRows = new RowDictionary(mediaTable); + + // set the disk identifiers and sources for files + foreach (var fileRow in tables["File"]?.Rows.Cast() ?? Enumerable.Empty()) + { + var xFile = this.GetIndexedElement("File", fileRow.File); + + // Don't bother processing files that are orphaned (and won't show up in the output anyway) + if (null != xFile.Parent) + { + // set the diskid + if (null != mediaTable) + { + foreach (MediaRow mediaRow in mediaTable.Rows) + { + if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) + { + xFile.SetAttributeValue("DiskId", mediaRow.DiskId); + break; + } + } + } + + var fileId = xFile?.Attribute("Id")?.Value; + var fileCompressed = xFile?.Attribute("Compressed")?.Value; + var fileShortName = xFile?.Attribute("ShortName")?.Value; + var fileName = xFile?.Attribute("Name")?.Value; + + // set the source (done here because it requires information from the Directory table) + if (OutputType.Module == this.OutputType) + { + xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_'))); + } + else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) + { + xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId)); + } + else // uncompressed + { + var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName; + + if (this.Compressed) // uncompressed at the root of the source image + { + xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name)); + } + else + { + var sourcePath = this.GetSourcePath(xFile); + xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name)); + } + } + } + } + + // set the file assemblies and manifests + foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty()) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) + { + foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) + { + xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2)); + xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3)); + xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32"); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); + } + } + + // nest the TypeLib elements + foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty()) + { + var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2)); + var xTypeLib = this.GetIndexedElement(row); + + foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) + { + xFile.Add(xTypeLib); + } + } + } + + /// + /// Finalize the MIME table. + /// + /// The collection of all tables. + /// + /// There is a foreign key shared between the MIME and Extension + /// tables so either one would be valid to be decompiled first, so + /// the only safe way to nest the MIME elements is to do it during finalize. + /// + private void FinalizeMIMETable(TableIndexedCollection tables) + { + var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty(); + foreach (var row in extensionRows) + { + // set the default MIME element for this extension + var mimeRef = row.FieldAsString(3); + if (null != mimeRef) + { + if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef)) + { + xMime.SetAttributeValue("Default", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); + } + } + } + + var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows); + + foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty()) + { + var xMime = this.GetIndexedElement(row); + + if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions)) + { + foreach (var extension in xExtensions) + { + extension.Add(xMime); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension")); + } + } + } + + /// + /// Finalize the ProgId table. + /// + /// The collection of all tables. + /// + /// Enumerates through all the Class rows, looking for child ProgIds (these are the + /// default ProgIds for a given Class). Then go through the ProgId table and add any + /// remaining ProgIds for each Class. This happens during finalize because there is + /// a circular dependency between the Class and ProgId tables. + /// + private void FinalizeProgIdTable(TableIndexedCollection tables) + { + // add the default ProgIds for each class (and index the class table) + var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty(); + + var classesByCLSID = this.IndexTableOneToMany(classRows); + + var addedProgIds = new Dictionary(); + + foreach (var row in classRows) + { + var clsid = row.FieldAsString(0); + var xClass = this.GetIndexedElement(row); + + if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3))) + { + if (addedProgIds.TryGetValue(xProgId, out var progid)) + { + this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid)); + } + else + { + xClass.Add(xProgId); + addedProgIds.Add(xProgId, clsid); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId")); + } + } + + // add the remaining non-default ProgId entries for each class + foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty()) + { + var clsid = row.FieldAsString(2); + var xProgId = this.GetIndexedElement(row); + + if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent) + { + if (classesByCLSID.TryGetValue(clsid, out var xClasses)) + { + foreach (var xClass in xClasses) + { + xClass.Add(xProgId); + addedProgIds.Add(xProgId, clsid); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class")); + } + } + } + + // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension + var componentsById = this.IndexTableOneToMany(tables, "Component"); + + foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty()) + { + var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2)); + + // Haven't added the progId yet and it doesn't have a parent progId + if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent) + { + if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents)) + { + foreach (var xComponent in xComponents) + { + xComponent.Add(xProgId); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); + } + } + } + } + + /// + /// Finalize the Property table. + /// + /// The collection of all tables. + /// + /// Removes properties that are generated from other entries. + /// + private void FinalizePropertyTable(TableIndexedCollection tables) + { + foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty()) + { + // If no other fields on the property are set we must have created it in the backend. + var bits = row.FieldAsInteger(1); + if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) + && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript) + && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0)) + && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value) + && xProperty.Attribute("Secure")?.Value != "yes" + && xProperty.Attribute("SuppressModularization")?.Value != "yes") + { + xProperty.Remove(); + } + } + } + + /// + /// Finalize the RemoveFile table. + /// + /// The collection of all tables. + /// + /// Sets the directory/property for each RemoveFile row. + /// + private void FinalizeRemoveFileTable(TableIndexedCollection tables) + { + foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty()) + { + var xRemove = this.GetIndexedElement(row); + var property = row.FieldAsString(3); + + if (this.TryGetIndexedElement("Directory", out var _, property)) + { + xRemove.SetAttributeValue("Directory", property); + } + else + { + xRemove.SetAttributeValue("Property", property); + } + } + } + + /// + /// Finalize the LockPermissions or MsiLockPermissionsEx table. + /// + /// The collection of all tables. + /// Which table to finalize. + /// + /// Nests the Permission elements below their parent elements. There are no declared foreign + /// keys for the parents of the LockPermissions table. + /// + private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName) + { + var createFoldersById = this.IndexTableOneToMany(tables, tableName); + + foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty()) + { + var id = row.FieldAsString(0); + var table = row.FieldAsString(1); + var xPermission = this.GetIndexedElement(row); + + if ("CreateFolder" == table) + { + if (createFoldersById.TryGetValue(id, out var xCreateFolders)) + { + foreach (var xCreateFolder in xCreateFolders) + { + xCreateFolder.Add(xPermission); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + else + { + if (this.TryGetIndexedElement(table, out var xParent, id)) + { + xParent.Add(xPermission); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + } + } + + /// + /// Finalize the LockPermissions table. + /// + /// The collection of all tables. + /// + /// Nests the Permission elements below their parent elements. There are no declared foreign + /// keys for the parents of the LockPermissions table. + /// + private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions"); + + /// + /// Finalize the MsiLockPermissionsEx table. + /// + /// The collection of all tables. + /// + /// Nests the PermissionEx elements below their parent elements. There are no declared foreign + /// keys for the parents of the MsiLockPermissionsEx table. + /// + private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx"); + + private static Dictionary> IndexTable(Table table, int keyColumn, int? dataColumn) + { + if (table == null) + { + return new Dictionary>(); + } + + return table.Rows + .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + } + + private static XElement FindComplianceDrive(XElement xSearch) + { + var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement); + if (null == xComplianceDrive) + { + xComplianceDrive = new XElement(Names.ComplianceDriveElement); + xSearch.Add(xComplianceDrive); + } + + return xComplianceDrive; + } + + /// + /// Finalize the search tables. + /// + /// The collection of all tables. + /// Does all the complex linking required for the search tables. + private void FinalizeSearchTables(TableIndexedCollection tables) + { + var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0); + var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null); + var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row); + + var xComplianceCheck = new XElement(Names.ComplianceCheckElement); + if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature))) + { + this.RootElement.Add(xComplianceCheck); + } + + // index the locator tables by their signatures + var locators = + new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" } + .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty()) + .ToLookup(row => row.FieldAsString(0), row => row) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + + // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) + foreach (var locatorRows in locators.Values) + { + var firstDrLocator = -1; + + for (var i = 0; i < locatorRows.Count; i++) + { + var locatorRow = (Row)locatorRows[i]; + + if ("DrLocator" == locatorRow.TableDefinition.Name) + { + if (-1 == firstDrLocator) + { + firstDrLocator = i; + } + + if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) + { + locatorRows.RemoveAt(i); + locatorRows.Insert(firstDrLocator, locatorRow); + break; + } + } + } + } + + var xUsedSearches = new HashSet(); + var xUnusedSearches = new Dictionary(); + + foreach (var signature in locators.Keys) + { + var locatorRows = locators[signature]; + var xSignatureSearches = new List(); + + foreach (var locatorRow in locatorRows) + { + var used = true; + var xSearch = this.GetIndexedElement(locatorRow); + + if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count) + { + foreach (var xSearchParent in xSignatureSearches) + { + if (!xUsedSearches.Contains(xSearch)) + { + xSearchParent.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else + { + var xFileSearchRef = new XElement(Names.FileSearchRefElement, + new XAttribute("Id", signature)); + + xSearchParent.Add(xFileSearchRef); + } + } + } + else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1)) + { + var parentSignature = locatorRow.FieldAsString(1); + + if ("CCP_DRIVE" == parentSignature) + { + if (appSearches.ContainsKey(signature) + && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) + { + foreach (var propertyId in appSearchPropertyIds) + { + var xProperty = this.EnsureProperty(propertyId); + + if (ccpSearches.ContainsKey(signature)) + { + xProperty.SetAttributeValue("ComplianceCheck", "yes"); + } + + var xComplianceDrive = FindComplianceDrive(xProperty); + + if (!xUsedSearches.Contains(xSearch)) + { + xComplianceDrive.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else + { + var directorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + XAttributeIfNotNull("Parent", locatorRow, 1), + XAttributeIfNotNull("Path", locatorRow, 2)); + + xComplianceDrive.Add(directorySearchRef); + xSignatureSearches.Add(directorySearchRef); + } + } + } + else if (ccpSearches.ContainsKey(signature)) + { + var xComplianceDrive = FindComplianceDrive(xComplianceCheck); + + if (!xUsedSearches.Contains(xSearch)) + { + xComplianceDrive.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else + { + var directorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + XAttributeIfNotNull("Parent", locatorRow, 1), + XAttributeIfNotNull("Path", locatorRow, 2)); + + xComplianceDrive.Add(directorySearchRef); + xSignatureSearches.Add(directorySearchRef); + } + } + } + else + { + var usedDrLocator = false; + + if (locators.TryGetValue(parentSignature, out var parentLocatorRows)) + { + foreach (var parentLocatorRow in parentLocatorRows) + { + if ("DrLocator" == parentLocatorRow.TableDefinition.Name) + { + var xParentSearch = this.GetIndexedElement(parentLocatorRow); + + if (xParentSearch.HasElements) + { + var parentDrLocatorRow = drLocators[xParentSearch]; + var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", parentSignature), + XAttributeIfNotNull("Parent", parentDrLocatorRow, 1), + XAttributeIfNotNull("Path", parentDrLocatorRow, 2)); + + xParentSearch = xDirectorySearchRef; + xUnusedSearches.Add(parentSignature, xDirectorySearchRef); + } + + if (!xUsedSearches.Contains(xSearch)) + { + xParentSearch.Add(xSearch); + xUsedSearches.Add(xSearch); + usedDrLocator = true; + } + else + { + var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + new XAttribute("Parent", parentSignature), + XAttributeIfNotNull("Path", locatorRow, 2)); + + xParentSearch.Add(xSearch); + usedDrLocator = true; + } + } + else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) + { + var xParentSearch = this.GetIndexedElement(parentLocatorRow); + + xParentSearch.Add(xSearch); + xUsedSearches.Add(xSearch); + usedDrLocator = true; + } + } + + // keep track of unused DrLocator rows + if (!usedDrLocator) + { + xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); + } + } + else + { + // TODO: warn + } + } + } + else if (appSearches.ContainsKey(signature) + && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) + { + foreach (var propertyId in appSearchPropertyIds) + { + var xProperty = this.EnsureProperty(propertyId); + + if (ccpSearches.ContainsKey(signature)) + { + xProperty.SetAttributeValue("ComplianceCheck", "yes"); + } + + if (!xUsedSearches.Contains(xSearch)) + { + xProperty.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else if ("RegLocator" == locatorRow.TableDefinition.Name) + { + var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, + new XAttribute("Id", signature)); + + xProperty.Add(xRegistrySearchRef); + xSignatureSearches.Add(xRegistrySearchRef); + } + else + { + // TODO: warn about unavailable Ref element + } + } + } + else if (ccpSearches.ContainsKey(signature)) + { + if (!xUsedSearches.Contains(xSearch)) + { + xComplianceCheck.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else if ("RegLocator" == locatorRow.TableDefinition.Name) + { + var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, + new XAttribute("Id", signature)); + + xComplianceCheck.Add(xRegistrySearchRef); + xSignatureSearches.Add(xRegistrySearchRef); + } + else + { + // TODO: warn about unavailable Ref element + } + } + else + { + if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch") + { + xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); + } + else + { + // TODO: warn + used = false; + } + } + + // keep track of the search elements for this signature so that nested searches go in the proper parents + if (used) + { + xSignatureSearches.Add(xSearch); + } + } + } + + // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. + foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key)) + { + var used = false; + + XElement xLeafDirectorySearch = null; + var xUnusedSearch = unusedSearch.Value; + var xParent = xUnusedSearch; + var updatedLeaf = true; + while (updatedLeaf) + { + updatedLeaf = false; + + var xDirectorySearch = xParent.Element(Names.DirectorySearchElement); + if (xDirectorySearch != null) + { + xParent = xLeafDirectorySearch = xDirectorySearch; + updatedLeaf = true; + } + } + + if (xLeafDirectorySearch != null) + { + var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value; + if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds)) + { + var xProperty = this.EnsureProperty(appSearchPropertyIds[0]); + xProperty.Add(xUnusedSearch); + used = true; + } + else if (ccpSearches.ContainsKey(leafDirectorySearchId)) + { + xComplianceCheck.Add(xUnusedSearch); + used = true; + } + else + { + // TODO: warn + } + } + + if (!used) + { + // TODO: warn + } + } + } + + /// + /// Finalize the Shortcut table. + /// + /// The collection of all tables. + /// + /// Sets Advertise to yes if Target points to a Feature. + /// Occurs during finalization because it has to check against every feature row. + /// + private void FinalizeShortcutTable(TableIndexedCollection tables) + { + var shortcutTable = tables["Shortcut"]; + if (null == shortcutTable) + { + return; + } + + foreach (var row in shortcutTable.Rows) + { + var xShortcut = this.GetIndexedElement(row); + + var target = row.FieldAsString(4); + + if (this.TryGetIndexedElement("Feature", out var _, target)) + { + xShortcut.SetAttributeValue("Advertise", "yes"); + this.SetPrimaryFeature(row, 4, 3); + } + else + { + // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element + xShortcut.SetAttributeValue("Target", target); + } + } + } + + /// + /// Finalize the sequence tables. + /// + /// The collection of all tables. + /// + /// Creates the sequence elements. Occurs during finalization because its + /// not known if sequences refer to custom actions or dialogs during decompilation. + /// + private void FinalizeSequenceTables(TableIndexedCollection tables) + { + // finalize the normal sequence tables + if (OutputType.Product == this.OutputType && !this.TreatProductAsModule) + { + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = sequenceTable.WindowsInstallerTableName(); + + // if suppressing UI elements, skip UI-related sequence tables + if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) + { + continue; + } + + var table = tables[sequenceTableName]; + + if (null != table) + { + var actionSymbols = new List(); + var needAbsoluteScheduling = this.SuppressRelativeActionSequencing; + var nonSequencedActionRows = new Dictionary(); + var suppressedRelativeActionRows = new Dictionary(); + + // create a sorted array of actions in this table + foreach (var row in table.Rows) + { + var action = row.FieldAsString(0); + var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action)); + + actionSymbol.Action = action; + + if (!row.IsColumnNull(1)) + { + actionSymbol.Condition = row.FieldAsString(1); + } + + actionSymbol.Sequence = row.FieldAsInteger(2); + + actionSymbol.SequenceTable = sequenceTable; + + actionSymbols.Add(actionSymbol); + } + actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList(); + + for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++) + { + var actionSymbol = actionSymbols[i]; + this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow); + + // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions + if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition) + { + WixActionSymbol previousActionSymbol = null; + WixActionSymbol nextActionSymbol = null; + + // find the previous action row if there is one + if (0 <= i - 1) + { + previousActionSymbol = actionSymbols[i - 1]; + } + + // find the next action row if there is one + if (actionSymbols.Count > i + 1) + { + nextActionSymbol = actionSymbols[i + 1]; + } + + // the logic for setting the before or after attribute for an action: + // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. + // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. + // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. + // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. + // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. + // 6. If this action is AppSearch and has all standard information, ignore it. + // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. + // 8. Everything must be absolutely sequenced. + if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence)) + { + needAbsoluteScheduling = true; + } + else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) + { + actionSymbol.Before = nextActionSymbol.Action; + } + else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence) + { + actionSymbol.After = previousActionSymbol.Action; + } + else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action) + { + actionSymbol.After = previousActionSymbol.Action; + } + else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) + { + actionSymbol.Before = nextActionSymbol.Action; + } + else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition) + { + // ignore an AppSearch row which has the WiX standard sequence and a standard condition + } + else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers + { + nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol); + } + else if (0 < actionSymbol.Sequence) + { + needAbsoluteScheduling = true; + } + } + else + { + suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol); + } + } + + // create the actions now that we know if they must be absolutely or relatively scheduled + foreach (var actionRow in actionSymbols) + { + var key = actionRow.Id.Id; + + if (needAbsoluteScheduling) + { + // remove any before/after information to ensure this is absolutely sequenced + actionRow.Before = null; + actionRow.After = null; + } + else if (nonSequencedActionRows.ContainsKey(key)) + { + // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) + actionRow.Sequence = 0; + } + else if (suppressedRelativeActionRows.ContainsKey(key)) + { + // skip the suppressed relatively scheduled action rows + continue; + } + + // create the action element + this.CreateActionElement(actionRow); + } + } + } + } + else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables + { + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = sequenceTable.WindowsInstallerTableName(); + + // if suppressing UI elements, skip UI-related sequence tables + if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) + { + continue; + } + + var table = tables[String.Concat("Module", sequenceTableName)]; + + if (null != table) + { + foreach (var row in table.Rows) + { + var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0))); + + actionRow.Action = row.FieldAsString(0); + + if (!row.IsColumnNull(1)) + { + actionRow.Sequence = row.FieldAsInteger(1); + } + + if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) + { + switch (row.FieldAsInteger(3)) + { + case 0: + actionRow.Before = row.FieldAsString(2); + break; + case 1: + actionRow.After = row.FieldAsString(2); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); + break; + } + } + + if (!row.IsColumnNull(4)) + { + actionRow.Condition = row.FieldAsString(4); + } + + actionRow.SequenceTable = sequenceTable; + + // create action elements for non-standard actions + if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before) + { + this.CreateActionElement(actionRow); + } + } + } + } + } + } + + /// + /// Finalize the Upgrade table. + /// + /// The collection of all tables. + /// + /// Decompile the rows from the Upgrade and LaunchCondition tables + /// created by the MajorUpgrade element. + /// + private void FinalizeUpgradeTable(TableIndexedCollection tables) + { + var launchConditionTable = tables["LaunchCondition"]; + var upgradeTable = tables["Upgrade"]; + string downgradeErrorMessage = null; + string disallowUpgradeErrorMessage = null; + + // find the DowngradePreventedCondition launch condition message + if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) + { + foreach (var launchRow in launchConditionTable.Rows) + { + if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0])) + { + downgradeErrorMessage = Convert.ToString(launchRow[1]); + } + else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0])) + { + disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); + } + } + } + + if (null != upgradeTable && 0 < upgradeTable.Rows.Count) + { + XElement xMajorUpgrade = null; + + foreach (UpgradeRow upgradeRow in upgradeTable.Rows) + { + if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty) + { + var attr = upgradeRow.Attributes; + var removeFeatures = upgradeRow.Remove; + xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); + + if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) + { + xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) + { + xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) + { + xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes"); + } + + if (!String.IsNullOrEmpty(removeFeatures)) + { + xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures); + } + } + else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) + { + xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); + xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage); + } + } + + if (xMajorUpgrade != null) + { + if (String.IsNullOrEmpty(downgradeErrorMessage)) + { + xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes"); + } + + if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + xMajorUpgrade.SetAttributeValue("Disallow", "yes"); + xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage); + } + + var scheduledType = DetermineMajorUpgradeScheduling(tables); + if (scheduledType != "afterInstallValidate") + { + xMajorUpgrade.SetAttributeValue("Schedule", scheduledType); + } + + this.RootElement.Add(xMajorUpgrade); + } + } + } + + /// + /// Finalize the Verb table. + /// + /// The collection of all tables. + /// + /// The Extension table is a foreign table for the Verb table, but the + /// foreign key is only part of the primary key of the Extension table, + /// so it needs special logic to be nested properly. + /// + private void FinalizeVerbTable(TableIndexedCollection tables) + { + var xExtensions = this.IndexTableOneToMany(tables["Extension"]); + + var verbTable = tables["Verb"]; + if (null != verbTable) + { + foreach (var row in verbTable.Rows) + { + if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions)) + { + var xVerb = this.GetIndexedElement(row); + + foreach (var xVerbExtension in xVerbExtensions) + { + xVerbExtension.Add(xVerb); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension")); + } + } + } + } + + /// + /// Get the path to a file in the source image. + /// + /// The file. + /// The path to the file in the source image. + private string GetSourcePath(XElement xFile) + { + var sourcePath = new StringBuilder(); + + var component = xFile.Parent; + + for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent) + { + string name; + + var dirSourceName = xDirectory.Attribute("SourceName")?.Value; + var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value; + var dirShortName = xDirectory.Attribute("ShortName")?.Value; + var dirName = xDirectory.Attribute("Name")?.Value; + + if (!this.ShortNames && null != dirSourceName) + { + name = dirSourceName; + } + else if (null != dirShortSourceName) + { + name = dirShortSourceName; + } + else if (!this.ShortNames || null == dirShortName) + { + name = dirName; + } + else + { + name = dirShortName; + } + + if (0 == sourcePath.Length) + { + sourcePath.Append(name); + } + else + { + sourcePath.Insert(0, Path.DirectorySeparatorChar); + sourcePath.Insert(0, name); + } + } + + return sourcePath.ToString(); + } + + /// + /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). + /// + /// The name of the table to resolve. + /// The unsorted table names. + /// The sorted table names. + private void ResolveTableDependencies(string tableName, List unsortedTableNames, HashSet sortedTableNames) + { + unsortedTableNames.Remove(tableName); + + foreach (var columnDefinition in this.TableDefinitions[tableName].Columns) + { + // no dependency to resolve because this column doesn't reference another table + if (null == columnDefinition.KeyTable) + { + continue; + } + + foreach (var keyTable in columnDefinition.KeyTable.Split(';')) + { + if (tableName == keyTable) + { + continue; // self-referencing dependency + } + else if (sortedTableNames.Contains(keyTable)) + { + continue; // dependent table has already been sorted + } + else if (!this.TableDefinitions.Contains(keyTable)) + { + this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); + } + else if (unsortedTableNames.Contains(keyTable)) + { + this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); + } + else + { + // found a circular dependency, so ignore it (this assumes that the tables will + // use a finalize method to nest their elements since the ordering will not be + // deterministic + } + } + } + + sortedTableNames.Add(tableName); + } + + /// + /// Get the names of the tables to process in the order they should be processed, according to their dependencies. + /// + /// A StringCollection containing the ordered table names. + private HashSet GetOrderedTableNames() + { + var orderedTableNames = new HashSet(); + var unsortedTableNames = new List(this.TableDefinitions.Select(t => t.Name)); + + // resolve the dependencies for each table + while (0 < unsortedTableNames.Count) + { + this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames); + } + + return orderedTableNames; + } + + /// + /// Initialize decompilation. + /// + /// The collection of all tables. + /// + private void InitializeDecompile(TableIndexedCollection tables, int codepage) + { + // reset all the state information + this.Compressed = false; + this.ShortNames = false; + + this.Singletons.Clear(); + this.IndexedElements.Clear(); + this.PatchTargetFiles.Clear(); + + // set the codepage if its not neutral (0) + if (0 != codepage) + { + this.RootElement.SetAttributeValue("Codepage", codepage); + } + + if (this.OutputType == OutputType.Module) + { + var table = tables["_SummaryInformation"]; + var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9); + this.ModularizationGuid = row?.FieldAsString(1); + } + + // index the rows from the extension libraries + var indexedExtensionTables = new Dictionary>(); +#if TODO_DECOMPILER_EXTENSIONS + foreach (IDecompilerExtension extension in this.extensions) + { + // Get the optional library from the extension with the rows to be removed. + Library library = extension.GetLibraryToRemove(this.tableDefinitions); + if (null != library) + { + foreach (var section in library.Sections) + { + foreach (Table table in section.Tables) + { + foreach (Row row in table.Rows) + { + string primaryKey; + string tableName; + + // the Actions table needs to be handled specially + if ("WixAction" == table.Name) + { + primaryKey = row.FieldAsString(1); + + if (OutputType.Module == this.outputType) + { + tableName = String.Concat("Module", row.FieldAsString(0)); + } + else + { + tableName = row.FieldAsString(0); + } + } + else + { + primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); + tableName = table.Name; + } + + if (null != primaryKey) + { + HashSet indexedExtensionRows; + if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) + { + indexedExtensionRows = new HashSet(); + indexedExtensionTables.Add(tableName, indexedExtensionRows); + } + + indexedExtensionRows.Add(primaryKey); + } + } + } + } + } + } +#endif + + // remove the rows from the extension libraries (to allow full round-tripping) + foreach (var kvp in indexedExtensionTables) + { + var tableName = kvp.Key; + var indexedExtensionRows = kvp.Value; + + var table = tables[tableName]; + if (null != table) + { + var originalRows = new RowDictionary(table); + + // remove the original rows so that they can be added back if they should remain + table.Rows.Clear(); + + foreach (var row in originalRows.Values) + { + if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) + { + table.Rows.Add(row); + } + } + } + } + } + + /// + /// Decompile the tables. + /// + /// The output being decompiled. + private void DecompileTables(WindowsInstallerData output) + { + var orderedTableNames = this.GetOrderedTableNames(); + foreach (var tableName in orderedTableNames) + { + var table = output.Tables[tableName]; + + // table does not exist in this database or should not be decompiled + if (null == table || !this.DecompilableTable(output, tableName)) + { + continue; + } + + this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name)); + + // empty tables may be kept with EnsureTable if the user set the proper option + if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) + { + this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name))); + } + + switch (table.Name) + { + case "_SummaryInformation": + // handled in FinalizeDecompile + break; + case "AdminExecuteSequence": + case "AdminUISequence": + case "AdvtExecuteSequence": + case "InstallExecuteSequence": + case "InstallUISequence": + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + // handled in FinalizeSequenceTables + break; + case "ActionText": + this.DecompileActionTextTable(table); + break; + case "AdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "AppId": + this.DecompileAppIdTable(table); + break; + case "AppSearch": + // handled in FinalizeSearchTables + break; + case "BBControl": + this.DecompileBBControlTable(table); + break; + case "Billboard": + this.DecompileBillboardTable(table); + break; + case "Binary": + this.DecompileBinaryTable(table); + break; + case "BindImage": + this.DecompileBindImageTable(table); + break; + case "CCPSearch": + // handled in FinalizeSearchTables + break; + case "CheckBox": + // handled in FinalizeCheckBoxTable + break; + case "Class": + this.DecompileClassTable(table); + break; + case "ComboBox": + this.DecompileComboBoxTable(table); + break; + case "Control": + this.DecompileControlTable(table); + break; + case "ControlCondition": + this.DecompileControlConditionTable(table); + break; + case "ControlEvent": + this.DecompileControlEventTable(table); + break; + case "CreateFolder": + this.DecompileCreateFolderTable(table); + break; + case "CustomAction": + this.DecompileCustomActionTable(table); + break; + case "CompLocator": + this.DecompileCompLocatorTable(table); + break; + case "Complus": + this.DecompileComplusTable(table); + break; + case "Component": + this.DecompileComponentTable(table); + break; + case "Condition": + this.DecompileConditionTable(table); + break; + case "Dialog": + this.DecompileDialogTable(table); + break; + case "Directory": + this.DecompileDirectoryTable(table); + break; + case "DrLocator": + this.DecompileDrLocatorTable(table); + break; + case "DuplicateFile": + this.DecompileDuplicateFileTable(table); + break; + case "Environment": + this.DecompileEnvironmentTable(table); + break; + case "Error": + this.DecompileErrorTable(table); + break; + case "EventMapping": + this.DecompileEventMappingTable(table); + break; + case "Extension": + this.DecompileExtensionTable(table); + break; + case "ExternalFiles": + this.DecompileExternalFilesTable(table); + break; + case "FamilyFileRanges": + // handled in FinalizeFamilyFileRangesTable + break; + case "Feature": + this.DecompileFeatureTable(table); + break; + case "FeatureComponents": + this.DecompileFeatureComponentsTable(table); + break; + case "File": + this.DecompileFileTable(table); + break; + case "FileSFPCatalog": + this.DecompileFileSFPCatalogTable(table); + break; + case "Font": + this.DecompileFontTable(table); + break; + case "Icon": + this.DecompileIconTable(table); + break; + case "ImageFamilies": + this.DecompileImageFamiliesTable(table); + break; + case "IniFile": + this.DecompileIniFileTable(table); + break; + case "IniLocator": + this.DecompileIniLocatorTable(table); + break; + case "IsolatedComponent": + this.DecompileIsolatedComponentTable(table); + break; + case "LaunchCondition": + this.DecompileLaunchConditionTable(table); + break; + case "ListBox": + this.DecompileListBoxTable(table); + break; + case "ListView": + this.DecompileListViewTable(table); + break; + case "LockPermissions": + this.DecompileLockPermissionsTable(table); + break; + case "Media": + this.DecompileMediaTable(table); + break; + case "MIME": + this.DecompileMIMETable(table); + break; + case "ModuleAdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "ModuleComponents": + // handled by DecompileComponentTable (since the ModuleComponents table + // rows are created by nesting components under the Module element) + break; + case "ModuleConfiguration": + this.DecompileModuleConfigurationTable(table); + break; + case "ModuleDependency": + this.DecompileModuleDependencyTable(table); + break; + case "ModuleExclusion": + this.DecompileModuleExclusionTable(table); + break; + case "ModuleIgnoreTable": + this.DecompileModuleIgnoreTableTable(table); + break; + case "ModuleSignature": + this.DecompileModuleSignatureTable(table); + break; + case "ModuleSubstitution": + this.DecompileModuleSubstitutionTable(table); + break; + case "MoveFile": + this.DecompileMoveFileTable(table); + break; + case "MsiAssembly": + // handled in FinalizeFileTable + break; + case "MsiDigitalCertificate": + this.DecompileMsiDigitalCertificateTable(table); + break; + case "MsiDigitalSignature": + this.DecompileMsiDigitalSignatureTable(table); + break; + case "MsiEmbeddedChainer": + this.DecompileMsiEmbeddedChainerTable(table); + break; + case "MsiEmbeddedUI": + this.DecompileMsiEmbeddedUITable(table); + break; + case "MsiLockPermissionsEx": + this.DecompileMsiLockPermissionsExTable(table); + break; + case "MsiPackageCertificate": + this.DecompileMsiPackageCertificateTable(table); + break; + case "MsiPatchCertificate": + this.DecompileMsiPatchCertificateTable(table); + break; + case "MsiShortcutProperty": + this.DecompileMsiShortcutPropertyTable(table); + break; + case "ODBCAttribute": + this.DecompileODBCAttributeTable(table); + break; + case "ODBCDataSource": + this.DecompileODBCDataSourceTable(table); + break; + case "ODBCDriver": + this.DecompileODBCDriverTable(table); + break; + case "ODBCSourceAttribute": + this.DecompileODBCSourceAttributeTable(table); + break; + case "ODBCTranslator": + this.DecompileODBCTranslatorTable(table); + break; + case "PatchMetadata": + this.DecompilePatchMetadataTable(table); + break; + case "PatchSequence": + this.DecompilePatchSequenceTable(table); + break; + case "ProgId": + this.DecompileProgIdTable(table); + break; + case "Properties": + this.DecompilePropertiesTable(table); + break; + case "Property": + this.DecompilePropertyTable(table); + break; + case "PublishComponent": + this.DecompilePublishComponentTable(table); + break; + case "RadioButton": + this.DecompileRadioButtonTable(table); + break; + case "Registry": + this.DecompileRegistryTable(table); + break; + case "RegLocator": + this.DecompileRegLocatorTable(table); + break; + case "RemoveFile": + this.DecompileRemoveFileTable(table); + break; + case "RemoveIniFile": + this.DecompileRemoveIniFileTable(table); + break; + case "RemoveRegistry": + this.DecompileRemoveRegistryTable(table); + break; + case "ReserveCost": + this.DecompileReserveCostTable(table); + break; + case "SelfReg": + this.DecompileSelfRegTable(table); + break; + case "ServiceControl": + this.DecompileServiceControlTable(table); + break; + case "ServiceInstall": + this.DecompileServiceInstallTable(table); + break; + case "SFPCatalog": + this.DecompileSFPCatalogTable(table); + break; + case "Shortcut": + this.DecompileShortcutTable(table); + break; + case "Signature": + this.DecompileSignatureTable(table); + break; + case "TargetFiles_OptionalData": + this.DecompileTargetFiles_OptionalDataTable(table); + break; + case "TargetImages": + this.DecompileTargetImagesTable(table); + break; + case "TextStyle": + this.DecompileTextStyleTable(table); + break; + case "TypeLib": + this.DecompileTypeLibTable(table); + break; + case "Upgrade": + this.DecompileUpgradeTable(table); + break; + case "UpgradedFiles_OptionalData": + this.DecompileUpgradedFiles_OptionalDataTable(table); + break; + case "UpgradedFilesToIgnore": + this.DecompileUpgradedFilesToIgnoreTable(table); + break; + case "UpgradedImages": + this.DecompileUpgradedImagesTable(table); + break; + case "UIText": + this.DecompileUITextTable(table); + break; + case "Verb": + this.DecompileVerbTable(table); + break; + + default: +#if TODO_DECOMPILER_EXTENSIONS + if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) + { + extension.DecompileTable(table); + } + else +#endif + if (!this.SuppressCustomTables) + { + this.DecompileCustomTable(table); + } + break; + } + } + } + + /// + /// Determine if a particular table should be decompiled with the current settings. + /// + /// The output being decompiled. + /// The name of a table. + /// true if the table should be decompiled; false otherwise. + private bool DecompilableTable(WindowsInstallerData output, string tableName) + { + switch (tableName) + { + case "ActionText": + case "BBControl": + case "Billboard": + case "CheckBox": + case "Control": + case "ControlCondition": + case "ControlEvent": + case "Dialog": + case "Error": + case "EventMapping": + case "RadioButton": + case "TextStyle": + case "UIText": + return !this.SuppressUI; + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleAdvtUISequence": + case "ModuleComponents": + case "ModuleConfiguration": + case "ModuleDependency": + case "ModuleIgnoreTable": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + case "ModuleExclusion": + case "ModuleSignature": + case "ModuleSubstitution": + if (OutputType.Module != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "ExternalFiles": + case "FamilyFileRanges": + case "ImageFamilies": + case "PatchMetadata": + case "PatchSequence": + case "Properties": + case "TargetFiles_OptionalData": + case "TargetImages": + case "UpgradedFiles_OptionalData": + case "UpgradedFilesToIgnore": + case "UpgradedImages": + if (OutputType.PatchCreation != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "MsiPatchHeaders": + case "MsiPatchMetadata": + case "MsiPatchOldAssemblyName": + case "MsiPatchOldAssemblyFile": + case "MsiPatchSequence": + case "Patch": + case "PatchPackage": + this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); + return false; + case "_SummaryInformation": + return true; + case "_Validation": + case "MsiAssemblyName": + case "MsiFileHash": + return false; + default: // all other tables are allowed in any output except for a patch creation package + if (OutputType.PatchCreation == output.Type) + { + this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + } + } + + /// + /// Decompile the _SummaryInformation table. + /// + /// The tables to decompile. + private void FinalizeSummaryInformationStream(TableIndexedCollection tables) + { + var table = tables["_SummaryInformation"]; + + if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) + { + var xSummaryInformation = new XElement(Names.SummaryInformationElement); + + foreach (var row in table.Rows) + { + var value = row.FieldAsString(1); + + if (!String.IsNullOrEmpty(value)) + { + switch (row.FieldAsInteger(0)) + { + case 1: + if ("1252" != value) + { + xSummaryInformation.SetAttributeValue("Codepage", value); + } + break; + case 3: + { + var productName = this.RootElement.Attribute("Name")?.Value; + if (value != productName) + { + xSummaryInformation.SetAttributeValue("Description", value); + } + break; + } + case 4: + { + var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value; + if (value != productManufacturer) + { + xSummaryInformation.SetAttributeValue("Manufacturer", value); + } + break; + } + case 5: + if ("Installer" != value) + { + xSummaryInformation.SetAttributeValue("Keywords", value); + } + break; + case 7: + var template = value.Split(';'); + if (0 < template.Length && 0 < template[template.Length - 1].Length) + { + this.RootElement.SetAttributeValue("Language", template[template.Length - 1]); + } + break; + case 14: + var installerVersion = row.FieldAsInteger(1); + // Default InstallerVersion. + if (installerVersion != 500) + { + this.RootElement.SetAttributeValue("InstallerVersion", installerVersion); + } + break; + case 15: + var wordCount = row.FieldAsInteger(1); + if (0x1 == (wordCount & 0x1)) + { + this.ShortNames = true; + if (OutputType.Product == this.OutputType) + { + this.RootElement.SetAttributeValue("ShortNames", "yes"); + } + } + + if (0x2 == (wordCount & 0x2)) + { + this.Compressed = true; + + if (OutputType.Product == this.OutputType) + { + this.RootElement.SetAttributeValue("Compressed", "yes"); + } + } + + if (OutputType.Product == this.OutputType) + { + if (0x8 == (wordCount & 0x8)) + { + this.RootElement.SetAttributeValue("Scope", "perUser"); + } + else + { + var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS"); + if (xAllUsers?.Attribute("Value")?.Value == "1") + { + xAllUsers?.Remove(); + } + } + } + + break; + } + } + } + + if (xSummaryInformation.HasAttributes) + { + this.RootElement.Add(xSummaryInformation); + } + } + else + { + var xPatchInformation = new XElement(Names.PatchInformationElement); + + foreach (var row in table.Rows) + { + var propertyId = row.FieldAsInteger(0); + var value = row.FieldAsString(1); + + if (!String.IsNullOrEmpty(value)) + { + switch (propertyId) + { + case 1: + if ("1252" != value) + { + xPatchInformation.SetAttributeValue("SummaryCodepage", value); + } + break; + case 3: + xPatchInformation.SetAttributeValue("Description", value); + break; + case 4: + xPatchInformation.SetAttributeValue("Manufacturer", value); + break; + case 5: + if ("Installer,Patching,PCP,Database" != value) + { + xPatchInformation.SetAttributeValue("Keywords", value); + } + break; + case 6: + xPatchInformation.SetAttributeValue("Comments", value); + break; + case 19: + var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); + switch (security) + { + case 0: + xPatchInformation.SetAttributeValue("ReadOnly", "no"); + break; + case 4: + xPatchInformation.SetAttributeValue("ReadOnly", "yes"); + break; + } + break; + } + } + } + + this.RootElement.Add(xPatchInformation); + } + } + + /// + /// Decompile the ActionText table. + /// + /// The table to decompile. + private void DecompileActionTextTable(Table table) + { + foreach (var row in table.Rows) + { + var progressText = new XElement(Names.ProgressTextElement, + new XAttribute("Action", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2))); + + this.UIElement.Add(progressText); + } + } + + /// + /// Decompile the AppId table. + /// + /// The table to decompile. + private void DecompileAppIdTable(Table table) + { + foreach (var row in table.Rows) + { + var appId = new XElement(Names.AppIdElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)), + row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"), + row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes")); + + this.RootElement.Add(appId); + this.IndexElement(row, appId); + } + } + + /// + /// Decompile the BBControl table. + /// + /// The table to decompile. + private void DecompileBBControlTable(Table table) + { + foreach (BBControlRow bbControlRow in table.Rows) + { + var xControl = new XElement(Names.ControlElement, + new XAttribute("Id", bbControlRow.BBControl), + new XAttribute("Type", bbControlRow.Type), + new XAttribute("X", bbControlRow.X), + new XAttribute("Y", bbControlRow.Y), + new XAttribute("Width", bbControlRow.Width), + new XAttribute("Height", bbControlRow.Height), + null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text)); + + if (null != bbControlRow[7]) + { + SetControlAttributes(bbControlRow.Attributes, xControl); + } + + if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard)) + { + xBillboard.Add(xControl); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); + } + } + } + + /// + /// Decompile the Billboard table. + /// + /// The table to decompile. + private void DecompileBillboardTable(Table table) + { + var billboards = new SortedList(); + + foreach (var row in table.Rows) + { + var xBillboard = new XElement(Names.BillboardElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Feature", row.FieldAsString(1))); + + this.IndexElement(row, xBillboard); + billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); + } + + var billboardActions = new Dictionary(); + + foreach (var row in billboards.Values) + { + var xBillboard = this.GetIndexedElement(row); + + if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction)) + { + xBillboardAction = new XElement(Names.BillboardActionElement, + new XAttribute("Id", row.FieldAsString(2))); + + this.UIElement.Add(xBillboardAction); + billboardActions.Add(row.FieldAsString(2), xBillboardAction); + } + + xBillboardAction.Add(xBillboard); + } + } + + /// + /// Decompile the Binary table. + /// + /// The table to decompile. + private void DecompileBinaryTable(Table table) + { + foreach (var row in table.Rows) + { + var xBinary = new XElement(Names.BinaryElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.RootElement.Add(xBinary); + } + } + + /// + /// Decompile the BindImage table. + /// + /// The table to decompile. + private void DecompileBindImageTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) + { + xFile.SetAttributeValue("BindPath", row.FieldAsString(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); + } + } + } + + /// + /// Decompile the Class table. + /// + /// The table to decompile. + private void DecompileClassTable(Table table) + { + foreach (var row in table.Rows) + { + var xClass = new XElement(Names.ClassElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Advertise", "yes"), + new XAttribute("Context", row.FieldAsString(1)), + row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)), + row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)), + row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)), + row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)), + row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)), + row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10))); + + if (!row.IsColumnNull(6)) + { + var fileTypeMaskStrings = row.FieldAsString(6).Split(';'); + + try + { + foreach (var fileTypeMaskString in fileTypeMaskStrings) + { + var fileTypeMaskParts = fileTypeMaskString.Split(','); + + if (4 == fileTypeMaskParts.Length) + { + var xFileTypeMask = new XElement(Names.FileTypeMaskElement, + new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)), + new XAttribute("Mask", fileTypeMaskParts[2]), + new XAttribute("Value", fileTypeMaskParts[3])); + + xClass.Add(xFileTypeMask); + } + else + { + // TODO: warn + } + } + } + catch (FormatException) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + } + catch (OverflowException) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + } + } + + if (!row.IsColumnNull(12)) + { + if (1 == row.FieldAsInteger(12)) + { + xClass.SetAttributeValue("RelativePath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); + } + } + + this.AddChildToParent("Component", xClass, row, 2); + this.IndexElement(row, xClass); + } + } + + /// + /// Decompile the ComboBox table. + /// + /// The table to decompile. + private void DecompileComboBoxTable(Table table) + { + // sort the combo boxes by their property and order + var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1))); + + XElement xComboBox = null; + string property = null; + foreach (var row in comboBoxRows) + { + if (null == xComboBox || row.FieldAsString(0) != property) + { + property = row.FieldAsString(0); + + xComboBox = new XElement(Names.ComboBoxElement, + new XAttribute("Property", property)); + + this.UIElement.Add(xComboBox); + } + + var xListItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); + xComboBox.Add(xListItem); + } + } + + /// + /// Decompile the Control table. + /// + /// The table to decompile. + private void DecompileControlTable(Table table) + { + foreach (ControlRow controlRow in table.Rows) + { + var xControl = new XElement(Names.ControlElement, + new XAttribute("Id", controlRow.Control), + new XAttribute("Type", controlRow.Type), + new XAttribute("X", controlRow.X), + new XAttribute("Y", controlRow.Y), + new XAttribute("Width", controlRow.Width), + new XAttribute("Height", controlRow.Height), + new XAttribute("Text", controlRow.Text)); + + if (!controlRow.IsColumnNull(7)) + { + string[] specialAttributes; + + // sets various common attributes like Disabled, Indirect, Integer, ... + SetControlAttributes(controlRow.Attributes, xControl); + + switch (controlRow.Type) + { + case "Bitmap": + specialAttributes = BitmapControlAttributes; + break; + case "CheckBox": + specialAttributes = CheckboxControlAttributes; + break; + case "ComboBox": + specialAttributes = ComboboxControlAttributes; + break; + case "DirectoryCombo": + specialAttributes = VolumeControlAttributes; + break; + case "Edit": + specialAttributes = EditControlAttributes; + break; + case "Icon": + specialAttributes = IconControlAttributes; + break; + case "ListBox": + specialAttributes = ListboxControlAttributes; + break; + case "ListView": + specialAttributes = ListviewControlAttributes; + break; + case "MaskedEdit": + specialAttributes = EditControlAttributes; + break; + case "PathEdit": + specialAttributes = EditControlAttributes; + break; + case "ProgressBar": + specialAttributes = ProgressControlAttributes; + break; + case "PushButton": + specialAttributes = ButtonControlAttributes; + break; + case "RadioButtonGroup": + specialAttributes = RadioControlAttributes; + break; + case "Text": + specialAttributes = TextControlAttributes; + break; + case "VolumeCostList": + specialAttributes = VolumeControlAttributes; + break; + case "VolumeSelectCombo": + specialAttributes = VolumeControlAttributes; + break; + default: + specialAttributes = null; + break; + } + + if (null != specialAttributes) + { + var iconSizeSet = false; + + for (var i = 16; 32 > i; i++) + { + if (1 == ((controlRow.Attributes >> i) & 1)) + { + string attribute = null; + + if (specialAttributes.Length > (i - 16)) + { + attribute = specialAttributes[i - 16]; + } + + // unknown attribute + if (null == attribute) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); + continue; + } + + switch (attribute) + { + case "Bitmap": + xControl.SetAttributeValue("Bitmap", "yes"); + break; + case "CDROM": + xControl.SetAttributeValue("CDROM", "yes"); + break; + case "ComboList": + xControl.SetAttributeValue("ComboList", "yes"); + break; + case "ElevationShield": + xControl.SetAttributeValue("ElevationShield", "yes"); + break; + case "Fixed": + xControl.SetAttributeValue("Fixed", "yes"); + break; + case "FixedSize": + xControl.SetAttributeValue("FixedSize", "yes"); + break; + case "Floppy": + xControl.SetAttributeValue("Floppy", "yes"); + break; + case "FormatSize": + xControl.SetAttributeValue("FormatSize", "yes"); + break; + case "HasBorder": + xControl.SetAttributeValue("HasBorder", "yes"); + break; + case "Icon": + xControl.SetAttributeValue("Icon", "yes"); + break; + case "Icon16": + if (iconSizeSet) + { + xControl.SetAttributeValue("IconSize", "48"); + } + else + { + iconSizeSet = true; + xControl.SetAttributeValue("IconSize", "16"); + } + break; + case "Icon32": + if (iconSizeSet) + { + xControl.SetAttributeValue("IconSize", "48"); + } + else + { + iconSizeSet = true; + xControl.SetAttributeValue("IconSize", "32"); + } + break; + case "Image": + xControl.SetAttributeValue("Image", "yes"); + break; + case "Multiline": + xControl.SetAttributeValue("Multiline", "yes"); + break; + case "NoPrefix": + xControl.SetAttributeValue("NoPrefix", "yes"); + break; + case "NoWrap": + xControl.SetAttributeValue("NoWrap", "yes"); + break; + case "Password": + xControl.SetAttributeValue("Password", "yes"); + break; + case "ProgressBlocks": + xControl.SetAttributeValue("ProgressBlocks", "yes"); + break; + case "PushLike": + xControl.SetAttributeValue("PushLike", "yes"); + break; + case "RAMDisk": + xControl.SetAttributeValue("RAMDisk", "yes"); + break; + case "Remote": + xControl.SetAttributeValue("Remote", "yes"); + break; + case "Removable": + xControl.SetAttributeValue("Removable", "yes"); + break; + case "ShowRollbackCost": + xControl.SetAttributeValue("ShowRollbackCost", "yes"); + break; + case "Sorted": + xControl.SetAttributeValue("Sorted", "yes"); + break; + case "Transparent": + xControl.SetAttributeValue("Transparent", "yes"); + break; + case "UserLanguage": + xControl.SetAttributeValue("UserLanguage", "yes"); + break; + default: + throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); + } + } + } + } + else if (0 < (controlRow.Attributes & 0xFFFF0000)) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); + } + } + + // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef + if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type)) + { + xControl.SetAttributeValue("Property", controlRow.Property); + } + + if (null != controlRow.Help) + { + var help = controlRow.Help.Split('|'); + + if (2 == help.Length) + { + if (0 < help[0].Length) + { + xControl.SetAttributeValue("ToolTip", help[0]); + } + + if (0 < help[1].Length) + { + xControl.SetAttributeValue("Help", help[1]); + } + } + } + + this.IndexElement(controlRow, xControl); + } + } + + /// + /// Decompile the ControlCondition table. + /// + /// The table to decompile. + private void DecompileControlConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) + { + switch (row.FieldAsString(2)) + { + case "Default": + xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3)); + break; + case "Disable": + xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3)); + break; + case "Enable": + xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3)); + break; + case "Hide": + xControl.SetAttributeValue("HideCondition", row.FieldAsString(3)); + break; + case "Show": + xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); + } + } + } + + /// + /// Decompile the ControlEvent table. + /// + /// The table to decompile. + private void DecompileControlEventTable(Table table) + { + var controlEvents = new SortedList(); + + foreach (var row in table.Rows) + { + var xPublish = new XElement(Names.PublishElement, + new XAttribute("Condition", row.FieldAsString(4))); + + var publishEvent = row.FieldAsString(2); + if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) + { + xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2)); + + if ("{}" != row.FieldAsString(3)) + { + xPublish.SetAttributeValue("Value", row.FieldAsString(3)); + } + } + else + { + xPublish.SetAttributeValue("Event", publishEvent); + xPublish.SetAttributeValue("Value", row.FieldAsString(3)); + } + + controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row); + + this.IndexElement(row, xPublish); + } + + foreach (var row in controlEvents.Values) + { + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) + { + var xPublish = this.GetIndexedElement(row); + xControl.Add(xPublish); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); + } + } + } + + /// + /// Decompile a custom table. + /// + /// The table to decompile. + private void DecompileCustomTable(Table table) + { + if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) + { + this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); + + var xCustomTable = new XElement(Names.CustomTableElement, + new XAttribute("Id", table.Name)); + + foreach (var columnDefinition in table.Definition.Columns) + { + var xColumn = new XElement(Names.ColumnElement, + new XAttribute("Id", columnDefinition.Name), + columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description), + columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable), + !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value), + !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"), + !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value), + !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value), + !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"), + !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"), + columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"), + new XAttribute("Width", columnDefinition.Length)); + + if (ColumnCategory.Unknown != columnDefinition.Category) + { + switch (columnDefinition.Category) + { + case ColumnCategory.Text: + xColumn.SetAttributeValue("Category", "text"); + break; + case ColumnCategory.UpperCase: + xColumn.SetAttributeValue("Category", "upperCase"); + break; + case ColumnCategory.LowerCase: + xColumn.SetAttributeValue("Category", "lowerCase"); + break; + case ColumnCategory.Integer: + xColumn.SetAttributeValue("Category", "integer"); + break; + case ColumnCategory.DoubleInteger: + xColumn.SetAttributeValue("Category", "doubleInteger"); + break; + case ColumnCategory.TimeDate: + xColumn.SetAttributeValue("Category", "timeDate"); + break; + case ColumnCategory.Identifier: + xColumn.SetAttributeValue("Category", "identifier"); + break; + case ColumnCategory.Property: + xColumn.SetAttributeValue("Category", "property"); + break; + case ColumnCategory.Filename: + xColumn.SetAttributeValue("Category", "filename"); + break; + case ColumnCategory.WildCardFilename: + xColumn.SetAttributeValue("Category", "wildCardFilename"); + break; + case ColumnCategory.Path: + xColumn.SetAttributeValue("Category", "path"); + break; + case ColumnCategory.Paths: + xColumn.SetAttributeValue("Category", "paths"); + break; + case ColumnCategory.AnyPath: + xColumn.SetAttributeValue("Category", "anyPath"); + break; + case ColumnCategory.DefaultDir: + xColumn.SetAttributeValue("Category", "defaultDir"); + break; + case ColumnCategory.RegPath: + xColumn.SetAttributeValue("Category", "regPath"); + break; + case ColumnCategory.Formatted: + xColumn.SetAttributeValue("Category", "formatted"); + break; + case ColumnCategory.FormattedSDDLText: + xColumn.SetAttributeValue("Category", "formattedSddl"); + break; + case ColumnCategory.Template: + xColumn.SetAttributeValue("Category", "template"); + break; + case ColumnCategory.Condition: + xColumn.SetAttributeValue("Category", "condition"); + break; + case ColumnCategory.Guid: + xColumn.SetAttributeValue("Category", "guid"); + break; + case ColumnCategory.Version: + xColumn.SetAttributeValue("Category", "version"); + break; + case ColumnCategory.Language: + xColumn.SetAttributeValue("Category", "language"); + break; + case ColumnCategory.Binary: + xColumn.SetAttributeValue("Category", "binary"); + break; + case ColumnCategory.CustomSource: + xColumn.SetAttributeValue("Category", "customSource"); + break; + case ColumnCategory.Cabinet: + xColumn.SetAttributeValue("Category", "cabinet"); + break; + case ColumnCategory.Shortcut: + xColumn.SetAttributeValue("Category", "shortcut"); + break; + default: + throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); + } + } + + if (ColumnModularizeType.None != columnDefinition.ModularizeType) + { + switch (columnDefinition.ModularizeType) + { + case ColumnModularizeType.Column: + xColumn.SetAttributeValue("Modularize", "Column"); + break; + case ColumnModularizeType.Condition: + xColumn.SetAttributeValue("Modularize", "Condition"); + break; + case ColumnModularizeType.Icon: + xColumn.SetAttributeValue("Modularize", "Icon"); + break; + case ColumnModularizeType.Property: + xColumn.SetAttributeValue("Modularize", "Property"); + break; + case ColumnModularizeType.SemicolonDelimited: + xColumn.SetAttributeValue("Modularize", "SemicolonDelimited"); + break; + default: + throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); + } + } + + if (ColumnType.Unknown != columnDefinition.Type) + { + switch (columnDefinition.Type) + { + case ColumnType.Localized: + xColumn.SetAttributeValue("Localizable", "yes"); + xColumn.SetAttributeValue("Type", "string"); + break; + case ColumnType.Number: + xColumn.SetAttributeValue("Type", "int"); + break; + case ColumnType.Object: + xColumn.SetAttributeValue("Type", "binary"); + break; + case ColumnType.Preserved: + case ColumnType.String: + xColumn.SetAttributeValue("Type", "string"); + break; + default: + throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'."); + } + } + + xCustomTable.Add(xColumn); + } + + foreach (var row in table.Rows) + { + var xRow = new XElement(Names.RowElement); + + foreach (var field in row.Fields.Where(f => f.Data != null)) + { + var xData = new XElement(Names.DataElement, + new XAttribute("Column", field.Column.Name), + new XAttribute("Value", field.AsString())); + + xRow.Add(xData); + } + + xCustomTable.Add(xRow); + } + + this.RootElement.Add(xCustomTable); + } + } + + /// + /// Decompile the CreateFolder table. + /// + /// The table to decompile. + private void DecompileCreateFolderTable(Table table) + { + foreach (var row in table.Rows) + { + var xCreateFolder = new XElement(Names.CreateFolderElement, + new XAttribute("Directory", row.FieldAsString(0))); + + this.AddChildToParent("Component", xCreateFolder, row, 1); + this.IndexElement(row, xCreateFolder); + } + } + + /// + /// Decompile the CustomAction table. + /// + /// The table to decompile. + private void DecompileCustomActionTable(Table table) + { + foreach (var row in table.Rows) + { + var xCustomAction = new XElement(Names.CustomActionElement, + new XAttribute("Id", row.FieldAsString(0))); + + var type = row.FieldAsInteger(1); + + if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)) + { + xCustomAction.SetAttributeValue("HideTarget", "yes"); + } + + if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate)) + { + xCustomAction.SetAttributeValue("Impersonate", "no"); + } + + if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware)) + { + xCustomAction.SetAttributeValue("TerminalServerAware", "yes"); + } + + if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) + { + xCustomAction.SetAttributeValue("Bitness", "always64"); + } + else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || + WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) + { + xCustomAction.SetAttributeValue("Bitness", "always32"); + } + + switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) + { + case 0: + // this is the default value + break; + case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: + xCustomAction.SetAttributeValue("Execute", "firstSequence"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: + xCustomAction.SetAttributeValue("Execute", "oncePerProcess"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: + xCustomAction.SetAttributeValue("Execute", "secondSequence"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript: + xCustomAction.SetAttributeValue("Execute", "deferred"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: + xCustomAction.SetAttributeValue("Execute", "rollback"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: + xCustomAction.SetAttributeValue("Execute", "commit"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits) + { + case 0: + // this is the default value + break; + case WindowsInstallerConstants.MsidbCustomActionTypeContinue: + xCustomAction.SetAttributeValue("Return", "ignore"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeAsync: + xCustomAction.SetAttributeValue("Return", "asyncWait"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: + xCustomAction.SetAttributeValue("Return", "asyncNoWait"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits; + switch (source) + { + case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: + xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: + if (!row.IsColumnNull(2)) + { + xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: + if (!row.IsColumnNull(2)) + { + xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeProperty: + xCustomAction.SetAttributeValue("Property", row.FieldAsString(2)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits) + { + case WindowsInstallerConstants.MsidbCustomActionTypeDll: + xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe: + xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeTextData: + if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) + { + xCustomAction.SetAttributeValue("Error", row.FieldAsString(3)); + } + else + { + xCustomAction.SetAttributeValue("Value", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeJScript: + if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) + { + xCustomAction.SetAttributeValue("Script", "jscript"); + // TODO: Extract to @ScriptFile? + // xCustomAction.Content = row.FieldAsString(3); + } + else + { + xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: + if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) + { + xCustomAction.SetAttributeValue("Script", "vbscript"); + // TODO: Extract to @ScriptFile? + // xCustomAction.Content = row.FieldAsString(3); + } + else + { + xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInstall: + this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + continue; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0; + if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall)) + { + xCustomAction.SetAttributeValue("PatchUninstall", "yes"); + } + + this.RootElement.Add(xCustomAction); + this.IndexElement(row, xCustomAction); + } + } + + /// + /// Decompile the CompLocator table. + /// + /// The table to decompile. + private void DecompileCompLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xComponentSearch = new XElement(Names.ComponentSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Guid", row.FieldAsString(1))); + + if (!row.IsColumnNull(2)) + { + switch (row.FieldAsInteger(2)) + { + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xComponentSearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + // this is the default value + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } + } + + this.IndexElement(row, xComponentSearch); + } + } + + /// + /// Decompile the Complus table. + /// + /// The table to decompile. + private void DecompileComplusTable(Table table) + { + foreach (var row in table.Rows) + { + if (!row.IsColumnNull(1)) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) + { + xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); + } + } + } + } + + /// + /// Decompile the Component table. + /// + /// The table to decompile. + private void DecompileComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var xComponent = new XElement(Names.ComponentElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty)); + + var attributes = row.FieldAsInteger(3); + + if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)) + { + xComponent.SetAttributeValue("Location", "source"); + } + else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional)) + { + xComponent.SetAttributeValue("Location", "either"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount)) + { + xComponent.SetAttributeValue("SharedDllRefCount", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent)) + { + xComponent.SetAttributeValue("Permanent", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive)) + { + xComponent.SetAttributeValue("Transitive", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite)) + { + xComponent.SetAttributeValue("NeverOverwrite", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) + { + xComponent.SetAttributeValue("Bitness", "always64"); + } + else + { + xComponent.SetAttributeValue("Bitness", "always32"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) + { + xComponent.SetAttributeValue("DisableRegistryReflection", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence)) + { + xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared)) + { + xComponent.SetAttributeValue("Shared", "yes"); + } + + if (!row.IsColumnNull(4)) + { + xComponent.SetAttributeValue("Condition", row.FieldAsString(4)); + } + + this.AddChildToParent("Directory", xComponent, row, 2); + this.IndexElement(row, xComponent); + } + } + + /// + /// Decompile the Condition table. + /// + /// The table to decompile. + private void DecompileConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0))) + { + var xLevel = new XElement(Names.LevelElement, + row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)), + new XAttribute("Level", row.FieldAsInteger(1))); + + xFeature.Add(xLevel); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature")); + } + } + } + + /// + /// Decompile the Dialog table. + /// + /// The table to decompile. + private void DecompileDialogTable(Table table) + { + foreach (var row in table.Rows) + { + var attributes = row.FieldAsNullableInteger(5) ?? 0; + + var xDialog = new XElement(Names.DialogElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("X", row.FieldAsString(1)), + new XAttribute("Y", row.FieldAsString(2)), + new XAttribute("Width", row.FieldAsString(3)), + new XAttribute("Height", row.FieldAsString(4)), + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null, + !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null); + + this.UIElement.Add(xDialog); + this.IndexElement(row, xDialog); + } + } + + /// + /// Decompile the Directory table. + /// + /// The table to decompile. + private void DecompileDirectoryTable(Table table) + { + foreach (var row in table.Rows) + { + var id = row.FieldAsString(0); + var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement; + var xDirectory = new XElement(elementName, + new XAttribute("Id", id)); + + if (!WindowsInstallerStandard.IsStandardDirectory(id)) + { + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + + if (id == "TARGETDIR" && names[0] != "SourceDir") + { + this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); + xDirectory.SetAttributeValue("Name", "SourceDir"); + } + else + { + if (null != names[0] && "." != names[0]) + { + if (null != names[1]) + { + xDirectory.SetAttributeValue("ShortName", names[0]); + } + else + { + xDirectory.SetAttributeValue("Name", names[0]); + } + } + + if (null != names[1]) + { + xDirectory.SetAttributeValue("Name", names[1]); + } + } + + if (null != names[2]) + { + if (null != names[3]) + { + xDirectory.SetAttributeValue("ShortSourceName", names[2]); + } + else + { + xDirectory.SetAttributeValue("SourceName", names[2]); + } + } + + if (null != names[3]) + { + xDirectory.SetAttributeValue("SourceName", names[3]); + } + } + + this.IndexElement(row, xDirectory); + } + + // nest the directories + foreach (var row in table.Rows) + { + var xDirectory = this.GetIndexedElement(row); + + var id = row.FieldAsString(0); + + if (id == "TARGETDIR") + { + // Skip TARGETDIR (but see below!). + } + else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.RootElement.Add(xDirectory); + } + else + { + var parentDirectoryId = row.FieldAsString(1); + + if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); + } + else if (xParentDirectory == xDirectory) // another way to specify a root directory + { + this.RootElement.Add(xDirectory); + } + else + { + // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root. + if (parentDirectoryId == "TARGETDIR") + { + this.RootElement.Add(xDirectory); + } + else + { + xParentDirectory.Add(xDirectory); + } + } + } + } + } + + /// + /// Decompile the DrLocator table. + /// + /// The table to decompile. + private void DecompileDrLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xDirectorySearch = new XElement(Names.DirectorySearchElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Path", row, 2), + XAttributeIfNotNull("Depth", row, 3)); + + this.IndexElement(row, xDirectorySearch); + } + } + + /// + /// Decompile the DuplicateFile table. + /// + /// The table to decompile. + private void DecompileDuplicateFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xCopyFile = new XElement(Names.CopyFileElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("FileId", row.FieldAsString(2))); + + if (!row.IsColumnNull(3)) + { + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); + if (null != names[0] && null != names[1]) + { + xCopyFile.SetAttributeValue("DestinationShortName", names[0]); + xCopyFile.SetAttributeValue("DestinationName", names[1]); + } + else if (null != names[0]) + { + xCopyFile.SetAttributeValue("DestinationName", names[0]); + } + } + + // destination directory/property is set in FinalizeDuplicateMoveFileTables + + this.AddChildToParent("Component", xCopyFile, row, 1); + this.IndexElement(row, xCopyFile); + } + } + + /// + /// Decompile the Environment table. + /// + /// The table to decompile. + private void DecompileEnvironmentTable(Table table) + { + foreach (var row in table.Rows) + { + var xEnvironment = new XElement(Names.EnvironmentElement, + new XAttribute("Id", row.FieldAsString(0))); + + var done = false; + var permanent = true; + var name = row.FieldAsString(1); + for (var i = 0; i < name.Length && !done; i++) + { + switch (name[i]) + { + case '=': + xEnvironment.SetAttributeValue("Action", "set"); + break; + case '+': + xEnvironment.SetAttributeValue("Action", "create"); + break; + case '-': + permanent = false; + break; + case '!': + xEnvironment.SetAttributeValue("Action", "remove"); + break; + case '*': + xEnvironment.SetAttributeValue("System", "yes"); + break; + default: + xEnvironment.SetAttributeValue("Name", name.Substring(i)); + done = true; + break; + } + } + + if (permanent) + { + xEnvironment.SetAttributeValue("Permanent", "yes"); + } + + if (!row.IsColumnNull(2)) + { + var value = row.FieldAsString(2); + + if (value.StartsWith("[~]", StringComparison.Ordinal)) + { + xEnvironment.SetAttributeValue("Part", "last"); + + if (3 < value.Length) + { + xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1)); + xEnvironment.SetAttributeValue("Value", value.Substring(4)); + } + } + else if (value.EndsWith("[~]", StringComparison.Ordinal)) + { + xEnvironment.SetAttributeValue("Part", "first"); + + if (3 < value.Length) + { + xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1)); + xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4)); + } + } + else + { + xEnvironment.SetAttributeValue("Value", value); + } + } + + this.AddChildToParent("Component", xEnvironment, row, 3); + } + } + + /// + /// Decompile the Error table. + /// + /// The table to decompile. + private void DecompileErrorTable(Table table) + { + foreach (var row in table.Rows) + { + var xError = new XElement(Names.ErrorElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Message", row.FieldAsString(1))); + + this.UIElement.Add(xError); + } + } + + /// + /// Decompile the EventMapping table. + /// + /// The table to decompile. + private void DecompileEventMappingTable(Table table) + { + foreach (var row in table.Rows) + { + var xSubscribe = new XElement(Names.SubscribeElement, + new XAttribute("Event", row.FieldAsString(2)), + new XAttribute("Attribute", row.FieldAsString(3))); + + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) + { + xControl.Add(xSubscribe); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); + } + } + } + + /// + /// Decompile the Extension table. + /// + /// The table to decompile. + private void DecompileExtensionTable(Table table) + { + foreach (var row in table.Rows) + { + var xExtension = new XElement(Names.ExtensionElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Advertise", "yes")); + + if (!row.IsColumnNull(3)) + { + if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3))) + { + xMime.SetAttributeValue("Default", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); + } + } + + if (!row.IsColumnNull(2)) + { + this.AddChildToParent("ProgId", xExtension, row, 2); + } + else + { + this.AddChildToParent("Component", xExtension, row, 1); + } + + this.IndexElement(row, xExtension); + } + } + + /// + /// Decompile the ExternalFiles table. + /// + /// The table to decompile. + private void DecompileExternalFilesTable(Table table) + { + foreach (var row in table.Rows) + { + var xExternalFile = new XElement(Names.ExternalFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Source", row.FieldAsString(2))); + + AddSymbolPaths(row, 3, xExternalFile); + + if (!row.IsColumnNull(4) && !row.IsColumnNull(5)) + { + var ignoreOffsets = row.FieldAsString(4).Split(','); + var ignoreLengths = row.FieldAsString(5).Split(','); + + if (ignoreOffsets.Length == ignoreLengths.Length) + { + for (var i = 0; i < ignoreOffsets.Length; i++) + { + var xIgnoreRange = new XElement(Names.IgnoreRangeElement); + + if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); + } + + if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); + } + + xExternalFile.Add(xIgnoreRange); + } + } + else + { + // TODO: warn + } + } + else if (!row.IsColumnNull(4) || !row.IsColumnNull(5)) + { + // TODO: warn about mismatch between columns + } + + // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable + + if (!row.IsColumnNull(7)) + { + xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7)); + } + + this.AddChildToParent("ImageFamilies", xExternalFile, row, 0); + this.IndexElement(row, xExternalFile); + } + } + + /// + /// Decompile the Feature table. + /// + /// The table to decompile. + private void DecompileFeatureTable(Table table) + { + var sortedFeatures = new SortedList(); + + foreach (var row in table.Rows) + { + var feature = new XElement(Names.FeatureElement, + new XAttribute("Id", row.FieldAsString(0)), + row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)), + new XAttribute("Level", row.FieldAsInteger(5)), + row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6))); + + if (row.IsColumnNull(4)) + { + feature.SetAttributeValue("Display", "hidden"); + } + else + { + var display = row.FieldAsInteger(4); + + if (0 == display) + { + feature.SetAttributeValue("Display", "hidden"); + } + else if (1 == display % 2) + { + feature.SetAttributeValue("Display", "expand"); + } + } + + var attributes = row.FieldAsInteger(7); + + if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) + { + // TODO: display a warning for setting favor local and follow parent together + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)) + { + feature.SetAttributeValue("InstallDefault", "source"); + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) + { + feature.SetAttributeValue("InstallDefault", "followParent"); + } + + if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise)) + { + feature.SetAttributeValue("InstallDefault", "advertise"); + } + + if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) && + WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) + { + this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); + feature.SetAttributeValue("AllowAdvertise", "no"); + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise)) + { + feature.SetAttributeValue("AllowAdvertise", "no"); + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) + { + feature.SetAttributeValue("AllowAdvertise", "system"); + } + + if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent)) + { + feature.SetAttributeValue("Absent", "disallow"); + } + + this.IndexElement(row, feature); + + // sort the features by their display column (and append the identifier to ensure unique keys) + sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row); + } + + // nest the features + foreach (var row in sortedFeatures.Values) + { + var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0)); + + if (row.IsColumnNull(1)) + { + this.RootElement.Add(xFeature); + } + else + { + if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1))) + { + if (xParentFeature == xFeature) + { + // TODO: display a warning about self-nesting + } + else + { + xParentFeature.Add(xFeature); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature")); + } + } + } + } + + /// + /// Decompile the FeatureComponents table. + /// + /// The table to decompile. + private void DecompileFeatureComponentsTable(Table table) + { + foreach (var row in table.Rows) + { + var xComponentRef = new XElement(Names.ComponentRefElement, + new XAttribute("Id", row.FieldAsString(1))); + + this.AddChildToParent("Feature", xComponentRef, row, 0); + this.IndexElement(row, xComponentRef); + } + } + + /// + /// Decompile the File table. + /// + /// The table to decompile. + private void DecompileFileTable(Table table) + { + foreach (FileRow fileRow in table.Rows) + { + var xFile = new XElement(Names.FileElement, + new XAttribute("Id", fileRow.File), + WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null, + null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null); + + var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName); + if (null != names[0] && null != names[1]) + { + xFile.SetAttributeValue("ShortName", names[0]); + xFile.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xFile.SetAttributeValue("Name", names[0]); + } + + if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) && + WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) + { + // TODO: error + } + else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed)) + { + xFile.SetAttributeValue("Compressed", "no"); + } + else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) + { + xFile.SetAttributeValue("Compressed", "yes"); + } + + this.IndexElement(fileRow, xFile); + } + } + + /// + /// Decompile the FileSFPCatalog table. + /// + /// The table to decompile. + private void DecompileFileSFPCatalogTable(Table table) + { + foreach (var row in table.Rows) + { + var xSfpFile = new XElement(Names.SFPFileElement, + new XAttribute("Id", row.FieldAsString(0))); + + this.AddChildToParent("SFPCatalog", xSfpFile, row, 1); + } + } + + /// + /// Decompile the Font table. + /// + /// The table to decompile. + private void DecompileFontTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) + { + if (!row.IsColumnNull(1)) + { + xFile.SetAttributeValue("FontTitle", row.FieldAsString(1)); + } + else + { + xFile.SetAttributeValue("TrueType", "yes"); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); + } + } + } + + /// + /// Decompile the Icon table. + /// + /// The table to decompile. + private void DecompileIconTable(Table table) + { + foreach (var row in table.Rows) + { + var icon = new XElement(Names.IconElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.RootElement.Add(icon); + } + } + + /// + /// Decompile the ImageFamilies table. + /// + /// The table to decompile. + private void DecompileImageFamiliesTable(Table table) + { + foreach (var row in table.Rows) + { + var family = new XElement(Names.FamilyElement, + new XAttribute("Name", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)), + row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5))); + + this.RootElement.Add(family); + this.IndexElement(row, family); + } + } + + /// + /// Decompile the IniFile table. + /// + /// The table to decompile. + private void DecompileIniFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xIniFile = new XElement(Names.IniFileElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Section", row.FieldAsString(3)), + new XAttribute("Key", row.FieldAsString(4)), + new XAttribute("Value", row.FieldAsString(5)), + row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2))); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + + if (null != names[0]) + { + if (null == names[1]) + { + xIniFile.SetAttributeValue("Name", names[0]); + } + else + { + xIniFile.SetAttributeValue("ShortName", names[0]); + } + } + + if (null != names[1]) + { + xIniFile.SetAttributeValue("Name", names[1]); + } + + switch (row.FieldAsInteger(6)) + { + case WindowsInstallerConstants.MsidbIniFileActionAddLine: + xIniFile.SetAttributeValue("Action", "addLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionCreateLine: + xIniFile.SetAttributeValue("Action", "createLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionAddTag: + xIniFile.SetAttributeValue("Action", "addTag"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + this.AddChildToParent("Component", xIniFile, row, 7); + } + } + + /// + /// Decompile the IniLocator table. + /// + /// The table to decompile. + private void DecompileIniLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xIniFileSearch = new XElement(Names.IniFileSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Section", row.FieldAsString(2)), + new XAttribute("Key", row.FieldAsString(3)), + row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4))); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + if (null != names[0] && null != names[1]) + { + xIniFileSearch.SetAttributeValue("ShortName", names[0]); + xIniFileSearch.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xIniFileSearch.SetAttributeValue("Name", names[0]); + } + + if (!row.IsColumnNull(5)) + { + switch (row.FieldAsInteger(5)) + { + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xIniFileSearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + // this is the default value + break; + case WindowsInstallerConstants.MsidbLocatorTypeRawValue: + xIniFileSearch.SetAttributeValue("Type", "raw"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + break; + } + } + + this.IndexElement(row, xIniFileSearch); + } + } + + /// + /// Decompile the IsolatedComponent table. + /// + /// The table to decompile. + private void DecompileIsolatedComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var xIsolateComponent = new XElement(Names.IsolateComponentElement, + new XAttribute("Shared", row.FieldAsString(0))); + + this.AddChildToParent("Component", xIsolateComponent, row, 1); + } + } + + /// + /// Decompile the LaunchCondition table. + /// + /// The table to decompile. + private void DecompileLaunchConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0)) + { + continue; // MajorUpgrade rows processed in FinalizeUpgradeTable + } + + var condition = new XElement(Names.LaunchElement, + new XAttribute("Condition", row.FieldAsString(0)), + new XAttribute("Message", row.FieldAsString(1))); + + this.RootElement.Add(condition); + } + } + + /// + /// Decompile the ListBox table. + /// + /// The table to decompile. + private void DecompileListBoxTable(Table table) + { + // sort the list boxes by their property and order + var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); + + XElement xListBox = null; + foreach (Row row in listBoxRows) + { + if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value) + { + xListBox = new XElement(Names.ListBoxElement, + new XAttribute("Property", row.FieldAsString(0))); + + this.UIElement.Add(xListBox); + } + + var listItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); + + xListBox.Add(listItem); + } + } + + /// + /// Decompile the ListView table. + /// + /// The table to decompile. + private void DecompileListViewTable(Table table) + { + // sort the list views by their property and order + var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); + + XElement xListView = null; + foreach (var row in listViewRows) + { + if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value) + { + xListView = new XElement(Names.ListViewElement, + new XAttribute("Property", row.FieldAsString(0))); + + this.UIElement.Add(xListView); + } + + var listItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4))); + + xListView.Add(listItem); + } + } + + /// + /// Decompile the LockPermissions table. + /// + /// The table to decompile. + private void DecompileLockPermissionsTable(Table table) + { + foreach (var row in table.Rows) + { + var xPermission = new XElement(Names.PermissionElement, + row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)), + new XAttribute("User", row.FieldAsString(3))); + + string[] specialPermissions; + + switch (row.FieldAsString(1)) + { + case "CreateFolder": + specialPermissions = LockPermissionConstants.FolderPermissions; + break; + case "File": + specialPermissions = LockPermissionConstants.FilePermissions; + break; + case "Registry": + specialPermissions = LockPermissionConstants.RegistryPermissions; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; + } + + var permissionBits = row.FieldAsInteger(4); + for (var i = 0; i < 32; i++) + { + if (0 != ((permissionBits >> i) & 1)) + { + string name = null; + + if (specialPermissions.Length > i) + { + name = specialPermissions[i]; + } + else if (16 > i && specialPermissions.Length <= i) + { + name = "SpecificRightsAll"; + } + else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16)) + { + name = LockPermissionConstants.StandardPermissions[i - 16]; + } + else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28)) + { + name = LockPermissionConstants.GenericPermissions[i - 28]; + } + + if (null == name) + { + this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); + } + else + { + switch (name) + { + case "Append": + case "ChangePermission": + case "CreateChild": + case "CreateFile": + case "CreateLink": + case "CreateSubkeys": + case "Delete": + case "DeleteChild": + case "EnumerateSubkeys": + case "Execute": + case "FileAllRights": + case "GenericAll": + case "GenericExecute": + case "GenericRead": + case "GenericWrite": + case "Notify": + case "Read": + case "ReadAttributes": + case "ReadExtendedAttributes": + case "ReadPermission": + case "SpecificRightsAll": + case "Synchronize": + case "TakeOwnership": + case "Traverse": + case "Write": + case "WriteAttributes": + case "WriteExtendedAttributes": + xPermission.SetAttributeValue(name, "yes"); + break; + default: + throw new InvalidOperationException($"Unknown permission attribute '{name}'."); + } + } + } + } + + this.IndexElement(row, xPermission); + } + } + + /// + /// Decompile the Media table. + /// + /// The table to decompile. + private void DecompileMediaTable(Table table) + { + foreach (MediaRow mediaRow in table.Rows) + { + var xMedia = new XElement(Names.MediaElement, + new XAttribute("Id", mediaRow.DiskId), + mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt), + mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel)); + + if (null != mediaRow.Cabinet) + { + var cabinet = mediaRow.Cabinet; + + if (cabinet.StartsWith("#", StringComparison.Ordinal)) + { + xMedia.SetAttributeValue("EmbedCab", "yes"); + cabinet = cabinet.Substring(1); + } + + xMedia.SetAttributeValue("Cabinet", cabinet); + } + + this.RootElement.Add(xMedia); + this.IndexElement(mediaRow, xMedia); + } + } + + /// + /// Decompile the MIME table. + /// + /// The table to decompile. + private void DecompileMIMETable(Table table) + { + foreach (var row in table.Rows) + { + var mime = new XElement(Names.MIMEElement, + new XAttribute("ContentType", row.FieldAsString(0)), + row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2))); + + this.IndexElement(row, mime); + } + } + + /// + /// Decompile the ModuleConfiguration table. + /// + /// The table to decompile. + private void DecompileModuleConfigurationTable(Table table) + { + foreach (var row in table.Rows) + { + var configuration = new XElement(Names.ConfigurationElement, + new XAttribute("Name", row.FieldAsString(0)), + XAttributeIfNotNull("Type", row, 2), + XAttributeIfNotNull("ContextData", row, 3), + XAttributeIfNotNull("DefaultValue", row, 4), + XAttributeIfNotNull("DisplayName", row, 6), + XAttributeIfNotNull("Description", row, 7), + XAttributeIfNotNull("HelpLocation", row, 8), + XAttributeIfNotNull("HelpKeyword", row, 9)); + + switch (row.FieldAsInteger(1)) + { + case 0: + configuration.SetAttributeValue("Format", "Text"); + break; + case 1: + configuration.SetAttributeValue("Format", "Key"); + break; + case 2: + configuration.SetAttributeValue("Format", "Integer"); + break; + case 3: + configuration.SetAttributeValue("Format", "Bitfield"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + if (!row.IsColumnNull(5)) + { + var attributes = row.FieldAsInteger(5); + + if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan)) + { + configuration.SetAttributeValue("KeyNoOrphan", "yes"); + } + + if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable)) + { + configuration.SetAttributeValue("NonNullable", "yes"); + } + + if (3 < attributes) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + } + } + + this.RootElement.Add(configuration); + } + } + + /// + /// Decompile the ModuleDependency table. + /// + /// The table to decompile. + private void DecompileModuleDependencyTable(Table table) + { + foreach (var row in table.Rows) + { + var xDependency = new XElement(Names.DependencyElement, + new XAttribute("RequiredId", row.FieldAsString(2)), + new XAttribute("RequiredLanguage", row.FieldAsString(3)), + XAttributeIfNotNull("RequiredVersion", row, 4)); + + this.RootElement.Add(xDependency); + } + } + + /// + /// Decompile the ModuleExclusion table. + /// + /// The table to decompile. + private void DecompileModuleExclusionTable(Table table) + { + foreach (var row in table.Rows) + { + var xExclusion = new XElement(Names.ExclusionElement, + new XAttribute("ExcludedId", row.FieldAsString(2)), + XAttributeIfNotNull("ExcludedMinVersion", row, 4), + XAttributeIfNotNull("ExcludedMaxVersion", row, 5)); + + var excludedLanguage = row.FieldAsInteger(3); + if (0 < excludedLanguage) + { + xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage); + } + else if (0 > excludedLanguage) + { + xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage); + } + + this.RootElement.Add(xExclusion); + } + } + + /// + /// Decompile the ModuleIgnoreTable table. + /// + /// The table to decompile. + private void DecompileModuleIgnoreTableTable(Table table) + { + foreach (var row in table.Rows) + { + var tableName = row.FieldAsString(0); + + // the linker automatically adds a ModuleIgnoreTable row for some tables + if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) + { + var xIgnoreTable = new XElement(Names.IgnoreTableElement, + new XAttribute("Id", tableName)); + + this.RootElement.Add(xIgnoreTable); + } + } + } + + /// + /// Decompile the ModuleSignature table. + /// + /// The table to decompile. + private void DecompileModuleSignatureTable(Table table) + { + if (1 == table.Rows.Count) + { + var row = table.Rows[0]; + + this.RootElement.SetAttributeValue("Id", row.FieldAsString(0)); + // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) + this.RootElement.SetAttributeValue("Language", row.FieldAsString(1)); + this.RootElement.SetAttributeValue("Version", row.FieldAsString(2)); + } + else + { + // TODO: warn + } + } + + /// + /// Decompile the ModuleSubstitution table. + /// + /// The table to decompile. + private void DecompileModuleSubstitutionTable(Table table) + { + foreach (var row in table.Rows) + { + var xSubstitution = new XElement(Names.SubstitutionElement, + new XAttribute("Table", row.FieldAsString(0)), + new XAttribute("Row", row.FieldAsString(1)), + new XAttribute("Column", row.FieldAsString(2)), + XAttributeIfNotNull("Value", row, 3)); + + this.RootElement.Add(xSubstitution); + } + } + + /// + /// Decompile the MoveFile table. + /// + /// The table to decompile. + private void DecompileMoveFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xCopyFile = new XElement(Names.CopyFileElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("SourceName", row, 2)); + + if (!row.IsColumnNull(3)) + { + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); + if (null != names[0] && null != names[1]) + { + xCopyFile.SetAttributeValue("DestinationShortName", names[0]); + xCopyFile.SetAttributeValue("DestinationName", names[1]); + } + else if (null != names[0]) + { + xCopyFile.SetAttributeValue("DestinationName", names[0]); + } + } + + // source/destination directory/property is set in FinalizeDuplicateMoveFileTables + + switch (row.FieldAsInteger(6)) + { + case 0: + break; + case WindowsInstallerConstants.MsidbMoveFileOptionsMove: + xCopyFile.SetAttributeValue("Delete", "yes"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + this.AddChildToParent("Component", xCopyFile, row, 1); + this.IndexElement(row, xCopyFile); + } + } + + /// + /// Decompile the MsiDigitalCertificate table. + /// + /// The table to decompile. + private void DecompileMsiDigitalCertificateTable(Table table) + { + foreach (var row in table.Rows) + { + var xDigitalCertificate = new XElement(Names.DigitalCertificateElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.IndexElement(row, xDigitalCertificate); + } + } + + /// + /// Decompile the MsiDigitalSignature table. + /// + /// The table to decompile. + private void DecompileMsiDigitalSignatureTable(Table table) + { + foreach (var row in table.Rows) + { + var xDigitalSignature = new XElement(Names.DigitalSignatureElement, + XAttributeIfNotNull("SourceFile", row, 3)); + + this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2); + + if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1))) + { + xParentElement.Add(xDigitalSignature); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0))); + } + } + } + + /// + /// Decompile the MsiEmbeddedChainer table. + /// + /// The table to decompile. + private void DecompileMsiEmbeddedChainerTable(Table table) + { + foreach (var row in table.Rows) + { + var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Condition", row.FieldAsString(1)), + XAttributeIfNotNull("CommandLine", row, 2)); + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: + xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: + xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: + xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.RootElement.Add(xEmbeddedChainer); + } + } + + /// + /// Decompile the MsiEmbeddedUI table. + /// + /// The table to decompile. + private void DecompileMsiEmbeddedUITable(Table table) + { + var xEmbeddedUI = new XElement(Names.EmbeddedUIElement); + + var foundEmbeddedUI = false; + var foundEmbeddedResources = false; + + foreach (var row in table.Rows) + { + var attributes = row.FieldAsInteger(2); + + if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI)) + { + if (foundEmbeddedUI) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + } + else + { + xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0)); + xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1)); + + var messageFilter = row.FieldAsInteger(3); + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT)) + { + xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR)) + { + xEmbeddedUI.SetAttributeValue("IgnoreError", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING)) + { + xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER)) + { + xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART)) + { + xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA)) + { + xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS)) + { + xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA)) + { + xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG)) + { + xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes"); + } + + if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic)) + { + xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes"); + } + + xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4)); + + this.UIElement.Add(xEmbeddedUI); + foundEmbeddedUI = true; + } + } + else + { + var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)), + new XAttribute("SourceFile", row.FieldAsString(4))); + + xEmbeddedUI.Add(xEmbeddedResource); + foundEmbeddedResources = true; + } + } + + if (!foundEmbeddedUI && foundEmbeddedResources) + { + // TODO: warn + } + } + + /// + /// Decompile the MsiLockPermissionsEx table. + /// + /// The table to decompile. + private void DecompileMsiLockPermissionsExTable(Table table) + { + foreach (var row in table.Rows) + { + var xPermissionEx = new XElement(Names.PermissionExElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Sddl", row.FieldAsString(3)), + XAttributeIfNotNull("Condition", row, 4)); + + switch (row.FieldAsString(2)) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; + } + + this.IndexElement(row, xPermissionEx); + } + } + + /// + /// Decompile the MsiPackageCertificate table. + /// + /// The table to decompile. + private void DecompileMsiPackageCertificateTable(Table table) + { + if (0 < table.Rows.Count) + { + var xPackageCertificates = new XElement(Names.PatchCertificatesElement); + this.RootElement.Add(xPackageCertificates); + this.AddCertificates(table, xPackageCertificates); + } + } + + /// + /// Decompile the MsiPatchCertificate table. + /// + /// The table to decompile. + private void DecompileMsiPatchCertificateTable(Table table) + { + if (0 < table.Rows.Count) + { + var xPatchCertificates = new XElement(Names.PatchCertificatesElement); + this.RootElement.Add(xPatchCertificates); + this.AddCertificates(table, xPatchCertificates); + } + } + + /// + /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. + /// + /// The table being decompiled. + /// DigitalCertificate parent + private void AddCertificates(Table table, XElement parent) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1))) + { + parent.Add(xDigitalCertificate); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate")); + } + } + } + + /// + /// Decompile the MsiShortcutProperty table. + /// + /// The table to decompile. + private void DecompileMsiShortcutPropertyTable(Table table) + { + foreach (var row in table.Rows) + { + var xProperty = new XElement(Names.ShortcutPropertyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + new XAttribute("Value", row.FieldAsString(3))); + + this.AddChildToParent("Shortcut", xProperty, row, 1); + } + } + + /// + /// Decompile the ODBCAttribute table. + /// + /// The table to decompile. + private void DecompileODBCAttributeTable(Table table) + { + foreach (var row in table.Rows) + { + var xProperty = new XElement(Names.PropertyElement, + new XAttribute("Id", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2))); + + this.AddChildToParent("ODBCDriver", xProperty, row, 0); + } + } + + /// + /// Decompile the ODBCDataSource table. + /// + /// The table to decompile. + private void DecompileODBCDataSourceTable(Table table) + { + foreach (var row in table.Rows) + { + var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("DriverName", row.FieldAsString(3))); + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: + xOdbcDataSource.SetAttributeValue("Registration", "machine"); + break; + case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: + xOdbcDataSource.SetAttributeValue("Registration", "user"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.IndexElement(row, xOdbcDataSource); + } + } + + /// + /// Decompile the ODBCDriver table. + /// + /// The table to decompile. + private void DecompileODBCDriverTable(Table table) + { + foreach (var row in table.Rows) + { + var xOdbcDriver = new XElement(Names.ODBCDriverElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("File", row.FieldAsString(3)), + XAttributeIfNotNull("SetupFile", row, 4)); + + this.AddChildToParent("Component", xOdbcDriver, row, 1); + this.IndexElement(row, xOdbcDriver); + } + } + + /// + /// Decompile the ODBCSourceAttribute table. + /// + /// The table to decompile. + private void DecompileODBCSourceAttributeTable(Table table) + { + foreach (var row in table.Rows) + { + var xProperty = new XElement(Names.PropertyElement, + new XAttribute("Id", row.FieldAsString(1)), + XAttributeIfNotNull("Value", row, 2)); + + this.AddChildToParent("ODBCDataSource", xProperty, row, 0); + } + } + + /// + /// Decompile the ODBCTranslator table. + /// + /// The table to decompile. + private void DecompileODBCTranslatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("File", row.FieldAsString(3)), + XAttributeIfNotNull("SetupFile", row, 4)); + + this.AddChildToParent("Component", xOdbcTranslator, row, 1); + } + } + + /// + /// Decompile the PatchMetadata table. + /// + /// The table to decompile. + private void DecompilePatchMetadataTable(Table table) + { + if (0 < table.Rows.Count) + { + var xPatchMetadata = new XElement(Names.PatchMetadataElement); + + foreach (var row in table.Rows) + { + var value = row.FieldAsString(2); + + switch (row.FieldAsString(1)) + { + case "AllowRemoval": + if ("1" == value) + { + xPatchMetadata.SetAttributeValue("AllowRemoval", "yes"); + } + break; + case "Classification": + if (null != value) + { + xPatchMetadata.SetAttributeValue("Classification", value); + } + break; + case "CreationTimeUTC": + if (null != value) + { + xPatchMetadata.SetAttributeValue("CreationTimeUTC", value); + } + break; + case "Description": + if (null != value) + { + xPatchMetadata.SetAttributeValue("Description", value); + } + break; + case "DisplayName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("DisplayName", value); + } + break; + case "ManufacturerName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("ManufacturerName", value); + } + break; + case "MinorUpdateTargetRTM": + if (null != value) + { + xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value); + } + break; + case "MoreInfoURL": + if (null != value) + { + xPatchMetadata.SetAttributeValue("MoreInfoURL", value); + } + break; + case "OptimizeCA": + var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement); + var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); + if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes"); + } + + if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes"); + } + + if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes"); + } + + xPatchMetadata.Add(xOptimizeCustomActions); + break; + case "OptimizedInstallMode": + if ("1" == value) + { + xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes"); + } + break; + case "TargetProductName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("TargetProductName", value); + } + break; + default: + var xCustomProperty = new XElement(Names.CustomPropertyElement, + XAttributeIfNotNull("Company", row, 0), + XAttributeIfNotNull("Property", row, 1), + XAttributeIfNotNull("Value", row, 2)); + + xPatchMetadata.Add(xCustomProperty); + break; + } + } + + this.RootElement.Add(xPatchMetadata); + } + } + + /// + /// Decompile the PatchSequence table. + /// + /// The table to decompile. + private void DecompilePatchSequenceTable(Table table) + { + foreach (var row in table.Rows) + { + var patchSequence = new XElement(Names.PatchSequenceElement, + new XAttribute("PatchFamily", row.FieldAsString(0))); + + if (!row.IsColumnNull(1)) + { + try + { + var guid = new Guid(row.FieldAsString(1)); + + patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1)); + } + catch // non-guid value + { + patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1)); + } + } + + if (!row.IsColumnNull(2)) + { + patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2)); + } + + if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3)) + { + patchSequence.SetAttributeValue("Supersede", "yes"); + } + + this.RootElement.Add(patchSequence); + } + } + + /// + /// Decompile the ProgId table. + /// + /// The table to decompile. + private void DecompileProgIdTable(Table table) + { + foreach (var row in table.Rows) + { + var xProgId = new XElement(Names.ProgIdElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Description", row, 3), + XAttributeIfNotNull("Icon", row, 4), + XAttributeIfNotNull("IconIndex", row, 5)); + + this.IndexElement(row, xProgId); + } + + // nest the ProgIds + foreach (var row in table.Rows) + { + var xProgId = this.GetIndexedElement(row); + + if (!row.IsColumnNull(1)) + { + this.AddChildToParent("ProgId", xProgId, row, 1); + } + else if (!row.IsColumnNull(2)) + { + // nesting is handled in FinalizeProgIdTable + } + else + { + // TODO: warn for orphaned ProgId + } + } + } + + /// + /// Decompile the Properties table. + /// + /// The table to decompile. + private void DecompilePropertiesTable(Table table) + { + foreach (var row in table.Rows) + { + var name = row.FieldAsString(0); + var value = row.FieldAsString(1); + + switch (name) + { + case "AllowProductCodeMismatches": + if ("1" == value) + { + this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes"); + } + break; + case "AllowProductVersionMajorMismatches": + if ("1" == value) + { + this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes"); + } + break; + case "ApiPatchingSymbolFlags": + if (null != value) + { + try + { + // remove the leading "0x" if its present + if (value.StartsWith("0x", StringComparison.Ordinal)) + { + value = value.Substring(2); + } + + this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16)); + } + catch + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + } + } + break; + case "DontRemoveTempFolderWhenFinished": + if ("1" == value) + { + this.RootElement.SetAttributeValue("CleanWorkingFolder", "no"); + } + break; + case "IncludeWholeFilesOnly": + if ("1" == value) + { + this.RootElement.SetAttributeValue("WholeFilesOnly", "yes"); + } + break; + case "ListOfPatchGUIDsToReplace": + if (null != value) + { + var guidRegex = 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}\}"); + var guidMatches = guidRegex.Matches(value); + + foreach (Match guidMatch in guidMatches) + { + var xReplacePatch = new XElement(Names.ReplacePatchElement, + new XAttribute("Id", guidMatch.Value)); + + this.RootElement.Add(xReplacePatch); + } + } + break; + case "ListOfTargetProductCodes": + if (null != value) + { + var targetProductCodes = value.Split(';'); + + foreach (var targetProductCodeString in targetProductCodes) + { + var xTargetProductCode = new XElement(Names.TargetProductCodeElement, + new XAttribute("Id", targetProductCodeString)); + + this.RootElement.Add(xTargetProductCode); + } + } + break; + case "PatchGUID": + this.RootElement.SetAttributeValue("Id", value); + break; + case "PatchSourceList": + this.RootElement.SetAttributeValue("SourceList", value); + break; + case "PatchOutputPath": + this.RootElement.SetAttributeValue("OutputPath", value); + break; + default: + var patchProperty = new XElement(Names.PatchPropertyElement, + new XAttribute("Name", name), + new XAttribute("Value", value)); + + this.RootElement.Add(patchProperty); + break; + } + } + } + + /// + /// Decompile the Property table. + /// + /// The table to decompile. + private void DecompilePropertyTable(Table table) + { + foreach (var row in table.Rows) + { + var id = row.FieldAsString(0); + var value = row.FieldAsString(1); + + if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) + { + if (0 < value.Length) + { + foreach (var propertyId in value.Split(';')) + { + if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId) + { + continue; + } + + var property = propertyId; + var suppressModulularization = false; + if (OutputType.Module == this.OutputType) + { + if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) + { + property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1); + } + else + { + suppressModulularization = true; + } + } + + var xSpecialProperty = this.EnsureProperty(property); + if (suppressModulularization) + { + xSpecialProperty.SetAttributeValue("SuppressModularization", "yes"); + } + + switch (id) + { + case "AdminProperties": + xSpecialProperty.SetAttributeValue("Admin", "yes"); + break; + case "MsiHiddenProperties": + xSpecialProperty.SetAttributeValue("Hidden", "yes"); + break; + case "SecureCustomProperties": + xSpecialProperty.SetAttributeValue("Secure", "yes"); + break; + } + } + } + + continue; + } + else if (OutputType.Product == this.OutputType) + { + switch (id) + { + case "Manufacturer": + this.RootElement.SetAttributeValue("Manufacturer", value); + continue; + case "ProductCode": + this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture)); + continue; + case "ProductLanguage": + this.RootElement.SetAttributeValue("Language", value); + continue; + case "ProductName": + this.RootElement.SetAttributeValue("Name", value); + continue; + case "ProductVersion": + this.RootElement.SetAttributeValue("Version", value); + continue; + case "UpgradeCode": + this.RootElement.SetAttributeValue("UpgradeCode", value); + continue; + } + } + + if (!this.SuppressUI || "ErrorDialog" != id) + { + var xProperty = this.EnsureProperty(id); + + xProperty.SetAttributeValue("Value", value); + } + } + } + + /// + /// Decompile the PublishComponent table. + /// + /// The table to decompile. + private void DecompilePublishComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var category = new XElement(Names.CategoryElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Qualifier", row.FieldAsString(1)), + XAttributeIfNotNull("AppData", row, 3)); + + this.AddChildToParent("Component", category, row, 2); + } + } + + /// + /// Decompile the RadioButton table. + /// + /// The table to decompile. + private void DecompileRadioButtonTable(Table table) + { + foreach (var row in table.Rows) + { + var radioButton = new XElement(Names.RadioButtonElement, + new XAttribute("Value", row.FieldAsString(2)), + new XAttribute("X", row.FieldAsInteger(3)), + new XAttribute("Y", row.FieldAsInteger(4)), + new XAttribute("Width", row.FieldAsInteger(5)), + new XAttribute("Height", row.FieldAsInteger(6)), + XAttributeIfNotNull("Text", row, 7)); + + if (!row.IsColumnNull(8)) + { + var help = (row.FieldAsString(8)).Split('|'); + + if (2 == help.Length) + { + if (0 < help[0].Length) + { + radioButton.SetAttributeValue("ToolTip", help[0]); + } + + if (0 < help[1].Length) + { + radioButton.SetAttributeValue("Help", help[1]); + } + } + } + + this.IndexElement(row, radioButton); + } + + // nest the radio buttons + var xRadioButtonGroups = new Dictionary(); + foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1))) + { + var xRadioButton = this.GetIndexedElement(row); + + if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup)) + { + xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement, + new XAttribute("Property", row.FieldAsString(0))); + + this.UIElement.Add(xRadioButtonGroup); + xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup); + } + + xRadioButtonGroup.Add(xRadioButton); + } + } + + /// + /// Decompile the Registry table. + /// + /// The table to decompile. + private void DecompileRegistryTable(Table table) + { + foreach (var row in table.Rows) + { + if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4)) + { + var xRegistryKey = new XElement(Names.RegistryKeyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2))); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRegistryKey.SetAttributeValue("Root", registryRootType); + } + + switch (row.FieldAsString(3)) + { + case "+": + xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); + break; + case "-": + xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); + break; + case "*": + xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); + xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); + break; + } + + this.IndexElement(row, xRegistryKey); + } + else + { + var xRegistryValue = new XElement(Names.RegistryValueElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRegistryValue.SetAttributeValue("Root", registryRootType); + } + + if (!row.IsColumnNull(4)) + { + var value = row.FieldAsString(4); + + if (value.StartsWith("#x", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "binary"); + xRegistryValue.SetAttributeValue("Value", value.Substring(2)); + } + else if (value.StartsWith("#%", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "expandable"); + xRegistryValue.SetAttributeValue("Value", value.Substring(2)); + } + else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "integer"); + xRegistryValue.SetAttributeValue("Value", value.Substring(1)); + } + else + { + if (value.StartsWith("##", StringComparison.Ordinal)) + { + value = value.Substring(1); + } + + if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "multiString"); + + if ("[~]" == value) + { + value = String.Empty; + } + else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) + { + value = value.Substring(3, value.Length - 6); + } + else if (value.StartsWith("[~]", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Action", "append"); + value = value.Substring(3); + } + else if (value.EndsWith("[~]", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Action", "prepend"); + value = value.Substring(0, value.Length - 3); + } + + var multiValues = NullSplitter.Split(value); + foreach (var multiValue in multiValues) + { + var xMultiStringValue = new XElement(Names.MultiStringElement, + new XAttribute("Value", multiValue)); + + xRegistryValue.Add(xMultiStringValue); + } + } + else + { + xRegistryValue.SetAttributeValue("Type", "string"); + xRegistryValue.SetAttributeValue("Value", value); + } + } + } + else + { + xRegistryValue.SetAttributeValue("Type", "string"); + xRegistryValue.SetAttributeValue("Value", String.Empty); + } + + this.IndexElement(row, xRegistryValue); + } + } + } + + /// + /// Decompile the RegLocator table. + /// + /// The table to decompile. + private void DecompileRegLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xRegistrySearch = new XElement(Names.RegistrySearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); + + switch (row.FieldAsInteger(1)) + { + case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: + xRegistrySearch.SetAttributeValue("Root", "HKCR"); + break; + case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: + xRegistrySearch.SetAttributeValue("Root", "HKCU"); + break; + case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: + xRegistrySearch.SetAttributeValue("Root", "HKLM"); + break; + case WindowsInstallerConstants.MsidbRegistryRootUsers: + xRegistrySearch.SetAttributeValue("Root", "HKU"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + if (row.IsColumnNull(4)) + { + xRegistrySearch.SetAttributeValue("Type", "file"); + } + else + { + var type = row.FieldAsInteger(4); + + if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) + { + xRegistrySearch.SetAttributeValue("Bitness", "always64"); + type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; + } + else + { + xRegistrySearch.SetAttributeValue("Bitness", "always32"); + } + + switch (type) + { + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xRegistrySearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + xRegistrySearch.SetAttributeValue("Type", "file"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeRawValue: + xRegistrySearch.SetAttributeValue("Type", "raw"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + } + + this.IndexElement(row, xRegistrySearch); + } + } + + /// + /// Decompile the RemoveFile table. + /// + /// The table to decompile. + private void DecompileRemoveFileTable(Table table) + { + foreach (var row in table.Rows) + { + if (row.IsColumnNull(2)) + { + var xRemoveFolder = new XElement(Names.RemoveFolderElement, + new XAttribute("Id", row.FieldAsString(0))); + + // directory/property is set in FinalizeDecompile + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: + xRemoveFolder.SetAttributeValue("On", "install"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: + xRemoveFolder.SetAttributeValue("On", "uninstall"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: + xRemoveFolder.SetAttributeValue("On", "both"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.AddChildToParent("Component", xRemoveFolder, row, 1); + this.IndexElement(row, xRemoveFolder); + } + else + { + var xRemoveFile = new XElement(Names.RemoveFileElement, + new XAttribute("Id", row.FieldAsString(0))); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + if (null != names[0] && null != names[1]) + { + xRemoveFile.SetAttributeValue("ShortName", names[0]); + xRemoveFile.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xRemoveFile.SetAttributeValue("Name", names[0]); + } + + // directory/property is set in FinalizeDecompile + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: + xRemoveFile.SetAttributeValue("On", "install"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: + xRemoveFile.SetAttributeValue("On", "uninstall"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: + xRemoveFile.SetAttributeValue("On", "both"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.AddChildToParent("Component", xRemoveFile, row, 1); + this.IndexElement(row, xRemoveFile); + } + } + } + + /// + /// Decompile the RemoveIniFile table. + /// + /// The table to decompile. + private void DecompileRemoveIniFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xIniFile = new XElement(Names.IniFileElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Directory", row, 2), + new XAttribute("Section", row.FieldAsString(3)), + new XAttribute("Key", row.FieldAsString(4)), + XAttributeIfNotNull("Value", row, 5)); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + if (null != names[0] && null != names[1]) + { + xIniFile.SetAttributeValue("ShortName", names[0]); + xIniFile.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xIniFile.SetAttributeValue("Name", names[0]); + } + + switch (row.FieldAsInteger(6)) + { + case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: + xIniFile.SetAttributeValue("Action", "removeLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: + xIniFile.SetAttributeValue("Action", "removeTag"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + this.AddChildToParent("Component", xIniFile, row, 7); + } + } + + /// + /// Decompile the RemoveRegistry table. + /// + /// The table to decompile. + private void DecompileRemoveRegistryTable(Table table) + { + foreach (var row in table.Rows) + { + if ("-" == row.FieldAsString(3)) + { + var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + new XAttribute("Action", "removeOnInstall")); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRemoveRegistryKey.SetAttributeValue("Root", registryRootType); + } + + this.AddChildToParent("Component", xRemoveRegistryKey, row, 4); + } + else + { + var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRemoveRegistryValue.SetAttributeValue("Root", registryRootType); + } + + this.AddChildToParent("Component", xRemoveRegistryValue, row, 4); + } + } + } + + /// + /// Decompile the ReserveCost table. + /// + /// The table to decompile. + private void DecompileReserveCostTable(Table table) + { + foreach (var row in table.Rows) + { + var xReserveCost = new XElement(Names.ReserveCostElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Directory", row, 2), + new XAttribute("RunLocal", row.FieldAsString(3)), + new XAttribute("RunFromSource", row.FieldAsString(4))); + + this.AddChildToParent("Component", xReserveCost, row, 4); + } + } + + /// + /// Decompile the SelfReg table. + /// + /// The table to decompile. + private void DecompileSelfRegTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) + { + xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); + } + } + } + + /// + /// Decompile the ServiceControl table. + /// + /// The table to decompile. + private void DecompileServiceControlTable(Table table) + { + foreach (var row in table.Rows) + { + var xServiceControl = new XElement(Names.ServiceControlElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1))); + + var eventValue = row.FieldAsInteger(2); + if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) && + WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) + { + xServiceControl.SetAttributeValue("Start", "both"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart)) + { + xServiceControl.SetAttributeValue("Start", "install"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) + { + xServiceControl.SetAttributeValue("Start", "uninstall"); + } + + if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) && + WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) + { + xServiceControl.SetAttributeValue("Stop", "both"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop)) + { + xServiceControl.SetAttributeValue("Stop", "install"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) + { + xServiceControl.SetAttributeValue("Stop", "uninstall"); + } + + if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) && + WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) + { + xServiceControl.SetAttributeValue("Remove", "both"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete)) + { + xServiceControl.SetAttributeValue("Remove", "install"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) + { + xServiceControl.SetAttributeValue("Remove", "uninstall"); + } + + if (!row.IsColumnNull(3)) + { + var arguments = NullSplitter.Split(row.FieldAsString(3)); + + foreach (var argument in arguments) + { + var xServiceArgument = new XElement(Names.ServiceArgumentElement, + new XAttribute("Value", argument)); + + xServiceControl.Add(xServiceArgument); + } + } + + if (!row.IsColumnNull(4)) + { + xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes"); + } + + this.AddChildToParent("Component", xServiceControl, row, 5); + } + } + + /// + /// Decompile the ServiceInstall table. + /// + /// The table to decompile. + private void DecompileServiceInstallTable(Table table) + { + foreach (var row in table.Rows) + { + var xServiceInstall = new XElement(Names.ServiceInstallElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)), + XAttributeIfNotNull("DisplayName", row, 2), + XAttributeIfNotNull("LoadOrderGroup", row, 6), + XAttributeIfNotNull("Account", row, 8), + XAttributeIfNotNull("Password", row, 9), + XAttributeIfNotNull("Arguments", row, 10), + XAttributeIfNotNull("Description", row, 12)); + + var serviceType = row.FieldAsInteger(3); + if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive)) + { + xServiceInstall.SetAttributeValue("Interactive", "yes"); + } + + if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) && + WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) + { + // TODO: warn + } + else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess)) + { + xServiceInstall.SetAttributeValue("Type", "ownProcess"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) + { + xServiceInstall.SetAttributeValue("Type", "shareProcess"); + } + + var startType = row.FieldAsInteger(4); + if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType) + { + xServiceInstall.SetAttributeValue("Start", "disabled"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType) + { + xServiceInstall.SetAttributeValue("Start", "demand"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType) + { + xServiceInstall.SetAttributeValue("Start", "auto"); + } + else + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + } + + var errorControl = row.FieldAsInteger(5); + if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical)) + { + xServiceInstall.SetAttributeValue("ErrorControl", "critical"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal)) + { + xServiceInstall.SetAttributeValue("ErrorControl", "normal"); + } + else + { + xServiceInstall.SetAttributeValue("ErrorControl", "ignore"); + } + + if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital)) + { + xServiceInstall.SetAttributeValue("Vital", "yes"); + } + + if (!row.IsColumnNull(7)) + { + var dependencies = NullSplitter.Split(row.FieldAsString(7)); + + foreach (var dependency in dependencies) + { + if (0 < dependency.Length) + { + var xServiceDependency = new XElement(Names.ServiceDependencyElement); + + if (dependency.StartsWith("+", StringComparison.Ordinal)) + { + xServiceDependency.SetAttributeValue("Group", "yes"); + xServiceDependency.SetAttributeValue("Id", dependency.Substring(1)); + } + else + { + xServiceDependency.SetAttributeValue("Id", dependency); + } + + xServiceInstall.Add(xServiceDependency); + } + } + } + + this.AddChildToParent("Component", xServiceInstall, row, 11); + this.IndexElement(row, xServiceInstall); + } + } + + /// + /// Decompile the SFPCatalog table. + /// + /// The table to decompile. + private void DecompileSFPCatalogTable(Table table) + { + foreach (var row in table.Rows) + { + var xSfpCatalog = new XElement(Names.SFPCatalogElement, + new XAttribute("Name", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.IndexElement(row, xSfpCatalog); + } + + // nest the SFPCatalog elements + foreach (var row in table.Rows) + { + var xSfpCatalog = this.GetIndexedElement(row); + + if (!row.IsColumnNull(2)) + { + if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2))) + { + xParentSFPCatalog.Add(xSfpCatalog); + } + else + { + xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2)); + + this.RootElement.Add(xSfpCatalog); + } + } + else + { + this.RootElement.Add(xSfpCatalog); + } + } + } + + /// + /// Decompile the Shortcut table. + /// + /// The table to decompile. + private void DecompileShortcutTable(Table table) + { + foreach (var row in table.Rows) + { + var xShortcut = new XElement(Names.ShortcutElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Directory", row.FieldAsString(1)), + XAttributeIfNotNull("Arguments", row, 5), + XAttributeIfNotNull("Description", row, 6), + XAttributeIfNotNull("Hotkey", row, 7), + XAttributeIfNotNull("Icon", row, 8), + XAttributeIfNotNull("IconIndex", row, 9), + XAttributeIfNotNull("WorkingDirectory", row, 11)); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + if (null != names[0] && null != names[1]) + { + xShortcut.SetAttributeValue("ShortName", names[0]); + xShortcut.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xShortcut.SetAttributeValue("Name", names[0]); + } + + if (!row.IsColumnNull(10)) + { + switch (row.FieldAsInteger(10)) + { + case 1: + xShortcut.SetAttributeValue("Show", "normal"); + break; + case 3: + xShortcut.SetAttributeValue("Show", "maximized"); + break; + case 7: + xShortcut.SetAttributeValue("Show", "minimized"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); + break; + } + } + + // Only try to read the MSI 4.0-specific columns if they actually exist + if (15 < row.Fields.Length) + { + if (!row.IsColumnNull(12)) + { + xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12)); + } + + if (null != row[13]) + { + xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13)); + } + + if (null != row[14]) + { + xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14)); + } + + if (null != row[15]) + { + xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15)); + } + } + + this.AddChildToParent("Component", xShortcut, row, 3); + this.IndexElement(row, xShortcut); + } + } + + /// + /// Decompile the Signature table. + /// + /// The table to decompile. + private void DecompileSignatureTable(Table table) + { + foreach (var row in table.Rows) + { + var fileSearch = new XElement(Names.FileSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("MinVersion", row, 2), + XAttributeIfNotNull("MaxVersion", row, 3), + XAttributeIfNotNull("MinSize", row, 4), + XAttributeIfNotNull("MaxSize", row, 5), + XAttributeIfNotNull("Languages", row, 8)); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + if (null != names[0]) + { + // it is permissable to just have a long name + if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1]) + { + fileSearch.SetAttributeValue("Name", names[0]); + } + else + { + fileSearch.SetAttributeValue("ShortName", names[0]); + } + } + + if (null != names[1]) + { + fileSearch.SetAttributeValue("Name", names[1]); + } + + if (!row.IsColumnNull(6)) + { + fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6))); + } + + if (!row.IsColumnNull(7)) + { + fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7))); + } + + this.IndexElement(row, fileSearch); + } + } + + /// + /// Decompile the TargetFiles_OptionalData table. + /// + /// The table to decompile. + private void DecompileTargetFiles_OptionalDataTable(Table table) + { + foreach (var row in table.Rows) + { + if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile)) + { + xPatchTargetFile = new XElement(Names.TargetFileElement, + new XAttribute("Id", row.FieldAsString(1))); + + if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0))) + { + xTargetImage.Add(xPatchTargetFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); + } + + this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile); + } + + AddSymbolPaths(row, 2, xPatchTargetFile); + + if (!row.IsColumnNull(3) && !row.IsColumnNull(4)) + { + var ignoreOffsets = row.FieldAsString(3).Split(','); + var ignoreLengths = row.FieldAsString(4).Split(','); + + if (ignoreOffsets.Length == ignoreLengths.Length) + { + for (var i = 0; i < ignoreOffsets.Length; i++) + { + var xIgnoreRange = new XElement(Names.IgnoreRangeElement); + + if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); + } + + if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); + } + + xPatchTargetFile.Add(xIgnoreRange); + } + } + else + { + // TODO: warn + } + } + else if (!row.IsColumnNull(3) || !row.IsColumnNull(4)) + { + // TODO: warn about mismatch between columns + } + + // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable + } + } + + /// + /// Decompile the TargetImages table. + /// + /// The table to decompile. + private void DecompileTargetImagesTable(Table table) + { + foreach (var row in table.Rows) + { + var xTargetImage = new XElement(Names.TargetImageElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1)), + new XAttribute("Order", row.FieldAsInteger(4)), + XAttributeIfNotNull("Validation", row, 5)); + + AddSymbolPaths(row, 2, xTargetImage); + + if (0 != row.FieldAsInteger(6)) + { + xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes"); + } + + this.AddChildToParent("UpgradedImages", xTargetImage, row, 3); + this.IndexElement(row, xTargetImage); + } + } + + /// + /// Decompile the TextStyle table. + /// + /// The table to decompile. + private void DecompileTextStyleTable(Table table) + { + foreach (var row in table.Rows) + { + var xTextStyle = new XElement(Names.TextStyleElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("FaceName", row.FieldAsString(1)), + new XAttribute("Size", row.FieldAsString(2))); + + if (!row.IsColumnNull(3)) + { + var color = row.FieldAsInteger(3); + + xTextStyle.SetAttributeValue("Red", color & 0xFF); + xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8); + xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16); + } + + if (!row.IsColumnNull(4)) + { + var styleBits = row.FieldAsInteger(4); + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold)) + { + xTextStyle.SetAttributeValue("Bold", "yes"); + } + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic)) + { + xTextStyle.SetAttributeValue("Italic", "yes"); + } + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline)) + { + xTextStyle.SetAttributeValue("Underline", "yes"); + } + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike)) + { + xTextStyle.SetAttributeValue("Strike", "yes"); + } + } + + this.UIElement.Add(xTextStyle); + } + } + + /// + /// Decompile the TypeLib table. + /// + /// The table to decompile. + private void DecompileTypeLibTable(Table table) + { + foreach (var row in table.Rows) + { + var id = row.FieldAsString(0); + var xTypeLib = new XElement(Names.TypeLibElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", id), + new XAttribute("Language", row.FieldAsInteger(1)), + XAttributeIfNotNull("Description", row, 4), + XAttributeIfNotNull("HelpDirectory", row, 5)); + + if (!row.IsColumnNull(3)) + { + var version = row.FieldAsInteger(3); + + if (65536 == version) + { + this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id)); + } + + xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8); + xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF); + } + + if (!row.IsColumnNull(7)) + { + xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7)); + } + + // nested under the appropriate File element in FinalizeFileTable + this.IndexElement(row, xTypeLib); + } + } + + /// + /// Decompile the Upgrade table. + /// + /// The table to decompile. + private void DecompileUpgradeTable(Table table) + { + var xUpgrades = new Dictionary(); + + foreach (UpgradeRow upgradeRow in table.Rows) + { + if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) + { + continue; // MajorUpgrade rows processed in FinalizeUpgradeTable + } + + if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade)) + { + xUpgrade = new XElement(Names.UpgradeElement, + new XAttribute("Id", upgradeRow.UpgradeCode)); + + this.RootElement.Add(xUpgrade); + xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade); + } + + var xUpgradeVersion = new XElement(Names.UpgradeVersionElement, + new XAttribute("Id", upgradeRow.UpgradeCode), + new XAttribute("Property", upgradeRow.ActionProperty)); + + if (null != upgradeRow.VersionMin) + { + xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin); + } + + if (null != upgradeRow.VersionMax) + { + xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax); + } + + if (null != upgradeRow.Language) + { + xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) + { + xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect)) + { + xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) + { + xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive)) + { + xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) + { + xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive)) + { + xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes"); + } + + if (null != upgradeRow.Remove) + { + xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove); + } + + xUpgrade.Add(xUpgradeVersion); + } + } + + /// + /// Decompile the UpgradedFiles_OptionalData table. + /// + /// The table to decompile. + private void DecompileUpgradedFiles_OptionalDataTable(Table table) + { + foreach (var row in table.Rows) + { + var xUpgradeFile = new XElement(Names.UpgradeFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Ignore", "no")); + + AddSymbolPaths(row, 2, xUpgradeFile); + + if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3)) + { + xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes"); + } + + if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4)) + { + xUpgradeFile.SetAttributeValue("WholeFile", "yes"); + } + + this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); + } + } + + /// + /// Decompile the UpgradedFilesToIgnore table. + /// + /// The table to decompile. + private void DecompileUpgradedFilesToIgnoreTable(Table table) + { + foreach (var row in table.Rows) + { + if ("*" != row.FieldAsString(0)) + { + var xUpgradeFile = new XElement(Names.UpgradeFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Ignore", "yes")); + + this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); + } + else + { + this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); + } + } + } + + /// + /// Decompile the UpgradedImages table. + /// + /// The table to decompile. + private void DecompileUpgradedImagesTable(Table table) + { + foreach (var row in table.Rows) + { + var xUpgradeImage = new XElement(Names.UpgradeImageElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1)), + XAttributeIfNotNull("SourcePatch", row, 2)); + + AddSymbolPaths(row, 3, xUpgradeImage); + + this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4); + this.IndexElement(row, xUpgradeImage); + } + } + + private static void AddSymbolPaths(Row row, int column, XElement xParent) + { + if (!row.IsColumnNull(column)) + { + var symbolPaths = row.FieldAsString(column).Split(';'); + + foreach (var symbolPath in symbolPaths) + { + var xSymbolPath = new XElement(Names.SymbolPathElement, + new XAttribute("Path", symbolPath)); + + xParent.Add(xSymbolPath); + } + } + } + + /// + /// Decompile the UIText table. + /// + /// The table to decompile. + private void DecompileUITextTable(Table table) + { + foreach (var row in table.Rows) + { + var xUiText = new XElement(Names.UITextElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Value", row.FieldAsString(1))); + + this.UIElement.Add(xUiText); + } + } + + /// + /// Decompile the Verb table. + /// + /// The table to decompile. + private void DecompileVerbTable(Table table) + { + foreach (var row in table.Rows) + { + var verb = new XElement(Names.VerbElement, + new XAttribute("Id", row.FieldAsString(1)), + XAttributeIfNotNull("Sequence", row, 2), + XAttributeIfNotNull("Command", row, 3), + XAttributeIfNotNull("Argument", row, 4)); + + this.IndexElement(row, verb); + } + } + + /// + /// Gets the RegistryRootType from an integer representation of the root. + /// + /// The source line information for the root. + /// The name of the table containing the field. + /// The field containing the root value. + /// The strongly-typed representation of the root. + /// true if the value could be converted; false otherwise. + private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType) + { + switch (Convert.ToInt32(field.Data)) + { + case (-1): + registryRootType = "HKMU"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: + registryRootType = "HKCR"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: + registryRootType = "HKCU"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: + registryRootType = "HKLM"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootUsers: + registryRootType = "HKU"; + return true; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); + registryRootType = null; // assign anything to satisfy the out parameter + return false; + } + } + + /// + /// Set the primary feature for a component. + /// + /// The row which specifies a primary feature. + /// The index of the column contaning the feature identifier. + /// The index of the column containing the component identifier. + private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) + { + // only products contain primary features + if (OutputType.Product == this.OutputType) + { + var featureField = row.Fields[featureColumnIndex]; + var componentField = row.Fields[componentColumnIndex]; + + if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data))) + { + xComponentRef.SetAttributeValue("Primary", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); + } + } + } + + /// + /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. + /// + /// The collection of all tables. + private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables) + { + var sequenceRemoveExistingProducts = 0; + var sequenceInstallValidate = 0; + var sequenceInstallInitialize = 0; + var sequenceInstallFinalize = 0; + var sequenceInstallExecute = 0; + var sequenceInstallExecuteAgain = 0; + + var installExecuteSequenceTable = tables["InstallExecuteSequence"]; + if (null != installExecuteSequenceTable) + { + var removeExistingProductsRow = -1; + for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) + { + var row = installExecuteSequenceTable.Rows[i]; + var action = row.FieldAsString(0); + var sequence = row.FieldAsInteger(2); + + switch (action) + { + case "RemoveExistingProducts": + sequenceRemoveExistingProducts = sequence; + removeExistingProductsRow = i; + break; + case "InstallValidate": + sequenceInstallValidate = sequence; + break; + case "InstallInitialize": + sequenceInstallInitialize = sequence; + break; + case "InstallExecute": + sequenceInstallExecute = sequence; + break; + case "InstallExecuteAgain": + sequenceInstallExecuteAgain = sequence; + break; + case "InstallFinalize": + sequenceInstallFinalize = sequence; + break; + } + } + + installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); + } + + if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) + { + return "afterInstallValidate"; + } + else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) + { + return "afterInstallInitialize"; + } + else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) + { + return "afterInstallExecute"; + } + else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) + { + return "afterInstallExecuteAgain"; + } + else + { + return "afterInstallFinalize"; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs new file mode 100644 index 00000000..db65bbf7 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs @@ -0,0 +1,160 @@ +namespace WixToolset.Core.WindowsInstaller.Decompile +{ + using System.Xml.Linq; + + internal static class Names + { + public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs"; + + public static readonly XName WixElement = WxsNamespace + "Wix"; + + public static readonly XName PackageElement = WxsNamespace + "Package"; + public static readonly XName ModuleElement = WxsNamespace + "Module"; + public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; + + public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation"; + + public static readonly XName CustomElement = WxsNamespace + "Custom"; + + public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; + public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence"; + public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence"; + public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence"; + public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence"; + + public static readonly XName AppSearchElement = WxsNamespace + "AppSearch"; + + public static readonly XName PropertyElement = WxsNamespace + "Property"; + + public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange"; + public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile"; + + public static readonly XName FileElement = WxsNamespace + "File"; + + public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; + public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; + + public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; + public static readonly XName UIElement = WxsNamespace + "UI"; + + public static readonly XName AppIdElement = WxsNamespace + "AppId"; + + public static readonly XName ControlElement = WxsNamespace + "Control"; + + public static readonly XName BillboardElement = WxsNamespace + "Billboard"; + public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction"; + + public static readonly XName BinaryElement = WxsNamespace + "Binary"; + + public static readonly XName ClassElement = WxsNamespace + "Class"; + + public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask"; + + public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox"; + + public static readonly XName ListItemElement = WxsNamespace + "ListItem"; + + public static readonly XName ConditionElement = WxsNamespace + "Condition"; + public static readonly XName PublishElement = WxsNamespace + "Publish"; + public static readonly XName CustomTableElement = WxsNamespace + "CustomTable"; + public static readonly XName ColumnElement = WxsNamespace + "Column"; + public static readonly XName RowElement = WxsNamespace + "Row"; + public static readonly XName DataElement = WxsNamespace + "Data"; + public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder"; + + public static readonly XName CustomActionElement = WxsNamespace + "CustomAction"; + + public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch"; + public static readonly XName ComponentElement = WxsNamespace + "Component"; + + public static readonly XName LevelElement = WxsNamespace + "Level"; + public static readonly XName DialogElement = WxsNamespace + "Dialog"; + public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory"; + public static readonly XName DirectoryElement = WxsNamespace + "Directory"; + public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; + public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; + public static readonly XName EnvironmentElement = WxsNamespace + "Environment"; + public static readonly XName ErrorElement = WxsNamespace + "Error"; + public static readonly XName SubscribeElement = WxsNamespace + "Subscribe"; + public static readonly XName ExtensionElement = WxsNamespace + "Extension"; + public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile"; + public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath"; + public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange"; + + public static readonly XName FeatureElement = WxsNamespace + "Feature"; + public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef"; + public static readonly XName SFPFileElement = WxsNamespace + "SFPFile"; + public static readonly XName IconElement = WxsNamespace + "Icon"; + public static readonly XName FamilyElement = WxsNamespace + "Family"; + public static readonly XName IniFileElement = WxsNamespace + "IniFile"; + public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch"; + public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent"; + public static readonly XName LaunchElement = WxsNamespace + "Launch"; + public static readonly XName ListBoxElement = WxsNamespace + "ListBox"; + public static readonly XName ListViewElement = WxsNamespace + "ListView"; + public static readonly XName PermissionElement = WxsNamespace + "Permission"; + public static readonly XName MediaElement = WxsNamespace + "Media"; + public static readonly XName MIMEElement = WxsNamespace + "MIME"; + public static readonly XName ConfigurationElement = WxsNamespace + "Configuration"; + public static readonly XName DependencyElement = WxsNamespace + "Dependency"; + public static readonly XName ExclusionElement = WxsNamespace + "Exclusion"; + public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable"; + public static readonly XName SubstitutionElement = WxsNamespace + "Substitution"; + public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate"; + public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature"; + public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer"; + public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI"; + public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource"; + public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx"; + public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates"; + public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates"; + public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty"; + public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource"; + public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver"; + public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator"; + public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata"; + public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions"; + public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty"; + public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence"; + public static readonly XName ProgIdElement = WxsNamespace + "ProgId"; + public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch"; + public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode"; + public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty"; + public static readonly XName CategoryElement = WxsNamespace + "Category"; + public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton"; + public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup"; + public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey"; + public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue"; + public static readonly XName MultiStringElement = WxsNamespace + "MultiString"; + public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch"; + public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder"; + public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile"; + public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey"; + public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue"; + public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost"; + public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl"; + public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument"; + public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall"; + public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency"; + public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog"; + public static readonly XName ShortcutElement = WxsNamespace + "Shortcut"; + public static readonly XName FileSearchElement = WxsNamespace + "FileSearch"; + public static readonly XName TargetFileElement = WxsNamespace + "TargetFile"; + public static readonly XName TargetImageElement = WxsNamespace + "TargetImage"; + public static readonly XName TextStyleElement = WxsNamespace + "TextStyle"; + public static readonly XName TypeLibElement = WxsNamespace + "TypeLib"; + public static readonly XName UpgradeElement = WxsNamespace + "Upgrade"; + public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion"; + public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile"; + public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage"; + public static readonly XName UITextElement = WxsNamespace + "UIText"; + public static readonly XName VerbElement = WxsNamespace + "Verb"; + public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck"; + public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef"; + public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive"; + public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef"; + public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef"; + public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade"; + //public static readonly XName Element = WxsNamespace + ""; + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs new file mode 100644 index 00000000..304d0152 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs @@ -0,0 +1,610 @@ +// 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 DELETE + +namespace WixToolset.Core.WindowsInstaller +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using WixToolset.Core.WindowsInstaller.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Creates a transform by diffing two outputs. + /// + public sealed class Differ + { + private readonly List inspectorExtensions; + private bool showPedanticMessages; + private bool suppressKeepingSpecialRows; + private bool preserveUnchangedRows; + private const char sectionDelimiter = '/'; + private readonly IMessaging messaging; + private SummaryInformationStreams transformSummaryInfo; + + /// + /// Instantiates a new Differ class. + /// + public Differ(IMessaging messaging) + { + this.inspectorExtensions = new List(); + this.messaging = messaging; + } + + /// + /// 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 WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput) + { + return this.Diff(targetOutput, updatedOutput, 0); + } + + /// + /// Creates a transform by diffing two outputs. + /// + /// The target output. + /// The updated output. + /// + /// The transform. + public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) + { + WindowsInstallerData transform = new WindowsInstallerData(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.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); + if (null != updatedOutput.SourceLineNumbers) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); + } + } + + // compare the output types + if (targetOutput.Type != updatedOutput.Type) + { + throw new WixException(ErrorMessages.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; + } + + /// + /// 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.messaging.Write(ErrorMessages.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(WindowsInstallerData 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.messaging.Write(ErrorMessages.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.messaging.Write(ErrorMessages.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.messaging.Write(ErrorMessages.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; } + } + } +} + +#endif diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs new file mode 100644 index 00000000..8305b5e6 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper + { + private readonly IBackendHelper backendHelper; + + public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) + { + this.backendHelper = serviceProvider.GetService(); + } + + #region IBackendHelper interfaces + + public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); + + public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); + + public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); + + public string CreateGuid() => this.backendHelper.CreateGuid(); + + public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); + + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); + + public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); + + public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); + + public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); + + public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); + + public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); + + public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); + + public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); + + public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); + + public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); + + public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); + + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); + + #endregion + + #region IWindowsInstallerBackendHelper interfaces + + public Row CreateRow(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinition tableDefinition) + { + var table = data.EnsureTable(tableDefinition); + + var row = table.CreateRow(symbol.SourceLineNumbers); + row.SectionId = section.Id; + + return row; + } + + public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions) + { + var tableDefinition = tableDefinitions.FirstOrDefault(t => t.SymbolDefinition?.Name == symbol.Definition.Name); + if (tableDefinition == null) + { + return false; + } + + var row = this.CreateRow(section, symbol, data, tableDefinition); + var rowOffset = 0; + + if (tableDefinition.SymbolIdIsPrimaryKey) + { + row[0] = symbol.Id.Id; + rowOffset = 1; + } + + for (var i = 0; i < symbol.Fields.Length; ++i) + { + if (i < tableDefinition.Columns.Length) + { + var column = tableDefinition.Columns[i + rowOffset]; + + switch (column.Type) + { + case ColumnType.Number: + row[i + rowOffset] = column.Nullable ? symbol.AsNullableNumber(i) : symbol.AsNumber(i); + break; + + default: + row[i + rowOffset] = symbol.AsString(i); + break; + } + } + } + + return true; + } + + #endregion + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs new file mode 100644 index 00000000..57f2f753 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs @@ -0,0 +1,272 @@ +// 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.Msi; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class InscribeMsiPackageCommand + { + public InscribeMsiPackageCommand(IInscribeContext context) + { + this.Context = context; + this.Messaging = context.ServiceProvider.GetService(); + this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); + this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + } + + private IInscribeContext Context { get; } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { 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 + var foundUnsignedExternals = false; + var shouldCommit = false; + + var attributes = File.GetAttributes(this.Context.InputFilePath); + if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) + { + this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath)); + return shouldCommit; + } + + using (var 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 + var codepage = 1252; + + // list of certificates for this database (hash/identifier) + var certificates = new Dictionary(); + + // Reset the in-memory tables for this new database + var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); + var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); + + // If any digital signature records exist that are not of the media type, preserve them + if (database.TableExists("MsiDigitalSignature")) + { + using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) + { + foreach (var digitalSignatureRecord in digitalSignatureView.Records) + { + Row digitalSignatureRow = null; + digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + var table = digitalSignatureRecord.GetString(0); + var 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 + var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); + var hashFileName = String.Concat(table, ".", signObject, ".bin"); + + Directory.CreateDirectory(hashPath); + hashPath = Path.Combine(hashPath, hashFileName); + + using (var fs = File.Create(hashPath)) + { + int bytesRead; + var 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 (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) + { + foreach (var digitalCertificateRecord in digitalCertificateView.Records) + { + var 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 + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + Directory.CreateDirectory(certPath); + certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer")); + + using (var fs = File.Create(certPath)) + { + int bytesRead; + var 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 + var 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 + var cert = X509Certificate.CreateFromCertFile(certPath); + var cert2 = new X509Certificate2(cert); + + certificates.Add(cert2.Thumbprint, certificateId); + } + } + } + + using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) + { + foreach (var mediaRecord in mediaView.Records) + { + X509Certificate2 cert2 = null; + Row digitalSignatureRow = null; + + var 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; + } + + var cabId = mediaRecord.GetString(1); // get the ID of the cab + var 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.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath)); + continue; + } + + try + { + // Get the certificate from the cab + var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); + cert2 = new X509Certificate2(signedFileCert); + } + catch (System.Security.Cryptography.CryptographicException e) + { + var 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.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + } + else // otherwise, generic error + { + this.Messaging.Write(ErrorMessages.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 + var certificateGeneratedId = this.WindowsInstallerBackendHelper.GenerateIdentifier("cer", cert2.Thumbprint); + + // Add it to our "add to MsiDigitalCertificate" table dictionary + var 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 + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + Directory.CreateDirectory(certPath); + certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer")); + File.Delete(certPath); + + using (var 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) + { + var command = new CreateIdtFileCommand(this.Messaging, digitalCertificateTable, codepage, this.Context.IntermediateFolder, true); + command.Execute(); + + database.Import(command.IdtPath); + shouldCommit = true; + } + + if (digitalSignatureTable.Rows.Count > 0) + { + var command = new CreateIdtFileCommand(this.Messaging, digitalSignatureTable, codepage, this.Context.IntermediateFolder, true); + command.Execute(); + + database.Import(command.IdtPath); + 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 not all of them were signed, give a warning + if (foundUnsignedExternals) + { + this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath)); + } + + if (shouldCommit) + { + database.Commit(); + } + } + + return shouldCommit; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs b/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs new file mode 100644 index 00000000..29e19e49 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs @@ -0,0 +1,399 @@ +// 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.CodeDom.Compiler; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + + /// + /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. + /// + public sealed class Melter + { +#if TODO_MELT + private MelterCore core; + private Decompiler decompiler; + + private Wix.ComponentGroup componentGroup; + private Wix.DirectoryRef primaryDirectoryRef; + private Wix.Fragment fragment; + + private string id; + private string moduleId; + private const string nullGuid = "{00000000-0000-0000-0000-000000000000}"; + + public string Id + { + get { return this.id; } + set { this.id = value; } + } + + public Decompiler Decompiler + { + get { return this.decompiler; } + set { this.decompiler = value; } + } + + /// + /// Creates a new melter object. + /// + /// The decompiler to use during the melting process. + /// The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id + public Melter(Decompiler decompiler, string id) + { + this.core = new MelterCore(); + + this.componentGroup = new Wix.ComponentGroup(); + this.fragment = new Wix.Fragment(); + this.primaryDirectoryRef = new Wix.DirectoryRef(); + + this.decompiler = decompiler; + this.id = id; + + if (null == this.decompiler) + { + this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process")); + } + } + + /// + /// Converts a Module wixout into a ComponentGroup. + /// + /// The output object representing the unbound merge module to melt. + /// The converted Module as a ComponentGroup. + public Wix.Wix Melt(Output wixout) + { + this.moduleId = GetModuleId(wixout); + + // Assign the default componentGroupId if none was specified + if (null == this.id) + { + this.id = this.moduleId; + } + + this.componentGroup.Id = this.id; + this.primaryDirectoryRef.Id = this.id; + + PreDecompile(wixout); + + wixout.Type = OutputType.Product; + this.decompiler.TreatProductAsModule = true; + Wix.Wix wix = this.decompiler.Decompile(wixout); + + if (null == wix) + { + return wix; + } + + ConvertModule(wix); + + return wix; + } + + /// + /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment. + /// + /// The output object representing an unbound merge module. + private void ConvertModule(Wix.Wix wix) + { + Wix.Product product = Melter.GetProduct(wix); + + List customActionsRemoved = new List(); + Dictionary customsToRemove = new Dictionary(); + + foreach (Wix.ISchemaElement child in product.Children) + { + Wix.Directory childDir = child as Wix.Directory; + if (null != childDir) + { + bool isTargetDir = this.WalkDirectory(childDir); + if (isTargetDir) + { + continue; + } + } + else + { + Wix.Dependency childDep = child as Wix.Dependency; + if (null != childDep) + { + this.AddPropertyRef(childDep.RequiredId); + continue; + } + else if (child is Wix.Package) + { + continue; + } + else if (child is Wix.CustomAction) + { + Wix.CustomAction customAction = child as Wix.CustomAction; + string directoryId; + if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id) + { + customActionsRemoved.Add(customAction.Id); + continue; + } + } + else if (child is Wix.InstallExecuteSequence) + { + Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence; + + foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children) + { + Wix.Custom custom = sequenceChild as Wix.Custom; + string directoryId; + if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId)) + { + customsToRemove.Add(custom, installExecuteSequence); + } + } + } + } + + this.fragment.AddChild(child); + } + + // For any customaction that we removed, also remove the scheduling of that action. + foreach (Wix.Custom custom in customsToRemove.Keys) + { + if (customActionsRemoved.Contains(custom.Action)) + { + ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom); + } + } + + AddProperty(this.moduleId, this.id); + + wix.RemoveChild(product); + wix.AddChild(this.fragment); + + this.fragment.AddChild(this.componentGroup); + this.fragment.AddChild(this.primaryDirectoryRef); + } + + /// + /// Gets the module from the Wix object. + /// + /// The Wix object. + /// The Module in the Wix object, null if no Module was found + private static Wix.Product GetProduct(Wix.Wix wix) + { + foreach (Wix.ISchemaElement element in wix.Children) + { + Wix.Product productElement = element as Wix.Product; + if (null != productElement) + { + return productElement; + } + } + return null; + } + + /// + /// Adds a PropertyRef to the main Fragment. + /// + /// Id of the PropertyRef. + private void AddPropertyRef(string propertyRefId) + { + Wix.PropertyRef propertyRef = new Wix.PropertyRef(); + propertyRef.Id = propertyRefId; + this.fragment.AddChild(propertyRef); + } + + /// + /// Adds a Property to the main Fragment. + /// + /// Id of the Property. + /// Value of the Property. + private void AddProperty(string propertyId, string value) + { + Wix.Property property = new Wix.Property(); + property.Id = propertyId; + property.Value = value; + this.fragment.AddChild(property); + } + + /// + /// Walks a directory structure obtaining Component Id's and Standard Directory Id's. + /// + /// The Directory to walk. + /// true if the directory is TARGETDIR. + private bool WalkDirectory(Wix.Directory directory) + { + bool isTargetDir = false; + if ("TARGETDIR" == directory.Id) + { + isTargetDir = true; + } + + string standardDirectoryId = null; + if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir) + { + this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId)); + } + + foreach (Wix.ISchemaElement child in directory.Children) + { + Wix.Directory childDir = child as Wix.Directory; + if (null != childDir) + { + if (isTargetDir) + { + this.primaryDirectoryRef.AddChild(child); + } + this.WalkDirectory(childDir); + } + else + { + Wix.Component childComponent = child as Wix.Component; + if (null != childComponent) + { + if (isTargetDir) + { + this.primaryDirectoryRef.AddChild(child); + } + this.AddComponentRef(childComponent); + } + } + } + + return isTargetDir; + } + + /// + /// Gets the module Id out of the Output object. + /// + /// The output object. + /// The module Id from the Output object. + private string GetModuleId(Output wixout) + { + // get the moduleId from the wixout + Table moduleSignatureTable = wixout.Tables["ModuleSignature"]; + if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count) + { + this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature")); + } + return moduleSignatureTable.Rows[0].Fields[0].Data.ToString(); + } + + /// + /// Determines if the directory Id starts with a standard directory id. + /// + /// The directory id. + /// The standard directory id. + /// true if the directory starts with a standard directory id. + private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId) + { + standardDirectoryId = null; + foreach (string id in WindowsInstallerStandard.GetStandardDirectories()) + { + if (directoryId.StartsWith(id, StringComparison.Ordinal)) + { + standardDirectoryId = id; + return true; + } + } + return false; + } + + /// + /// Adds a ComponentRef to the main ComponentGroup. + /// + /// The component to add. + private void AddComponentRef(Wix.Component component) + { + Wix.ComponentRef componentRef = new Wix.ComponentRef(); + componentRef.Id = component.Id; + this.componentGroup.AddChild(componentRef); + } + + /// + /// Adds a SetProperty CA for a Directory. + /// + /// The Id of the Property to set. + /// The value to set the Property to. + private void AddSetPropertyCustomAction(string propertyId, string value) + { + // Add the action + Wix.CustomAction customAction = new Wix.CustomAction(); + customAction.Id = propertyId; + customAction.Property = propertyId; + customAction.Value = value; + this.fragment.AddChild(customAction); + + // Schedule the action + Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence(); + Wix.Custom custom = new Wix.Custom(); + custom.Action = customAction.Id; + custom.Before = "CostInitialize"; + installExecuteSequence.AddChild(custom); + this.fragment.AddChild(installExecuteSequence); + } + + /// + /// Does any operations to the wixout that would need to be done before decompiling. + /// + /// The output object representing the unbound merge module. + private void PreDecompile(Output wixout) + { + string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id); + + foreach (Table table in wixout.Tables) + { + // Determine if the table has a feature foreign key + bool hasFeatureForeignKey = false; + foreach (ColumnDefinition columnDef in table.Definition.Columns) + { + if (null != columnDef.KeyTable) + { + string[] keyTables = columnDef.KeyTable.Split(';'); + foreach (string keyTable in keyTables) + { + if ("Feature" == keyTable) + { + hasFeatureForeignKey = true; + break; + } + } + } + } + + // If this table has no foreign keys to the feature table, skip it. + if (!hasFeatureForeignKey) + { + continue; + } + + // Go through all the rows and replace the null guid with the wix variable + // for columns that are foreign keys into the feature table. + foreach (Row row in table.Rows) + { + foreach (Field field in row.Fields) + { + if (null != field.Column.KeyTable) + { + string[] keyTables = field.Column.KeyTable.Split(';'); + foreach (string keyTable in keyTables) + { + if ("Feature" == keyTable) + { + field.Data = field.Data.ToString().Replace(nullGuid, wixVariable); + break; + } + } + } + } + } + } + } +#endif + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs b/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs new file mode 100644 index 00000000..034c9465 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs @@ -0,0 +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 +{ + using WixToolset.Data; + + /// + /// Melts a Module Wix document into a ComponentGroup representation. + /// + public sealed class MelterCore + { +#if TODO_MELT + /// + /// Gets whether the melter core encountered an error while processing. + /// + /// Flag if core encountered an error during processing. + public bool EncounteredError + { + get { return Messaging.Instance.EncounteredError; } + } + + /// + /// Sends a message to the message delegate if there is one. + /// + /// Message event arguments. + public void OnMessage(MessageEventArgs e) + { + Messaging.Instance.OnMessage(e); + } +#endif + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs new file mode 100644 index 00000000..3bd58c25 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -0,0 +1,85 @@ +// 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.Decompile; + using WixToolset.Core.WindowsInstaller.Inscribe; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class MsiBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + IBindResult result = null; + var dispose = true; + try + { + var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); + result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + dispose = false; + return result; + } + finally + { + if (dispose) + { + result?.Dispose(); + } + } + } + + public IDecompileResult Decompile(IDecompileContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendDecompile(context); + } + + var command = new DecompileMsiOrMsmCommand(context, backendExtensions); + var result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendDecompile(result); + } + + return result; + } + + public bool Inscribe(IInscribeContext context) + { + var command = new InscribeMsiPackageCommand(context); + return command.Execute(); + } + + public Intermediate Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs new file mode 100644 index 00000000..4927ee8c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -0,0 +1,76 @@ +// 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.Decompile; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class MsmBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + IBindResult result = null; + try + { + var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); + result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; + } + catch + { + result?.Dispose(); + throw; + } + } + + public IDecompileResult Decompile(IDecompileContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendDecompile(context); + } + + var command = new DecompileMsiOrMsmCommand(context, backendExtensions); + var result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendDecompile(result); + } + + return result; + } + + public bool Inscribe(IInscribeContext context) => false; + + public Intermediate Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs new file mode 100644 index 00000000..c46b6027 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -0,0 +1,162 @@ +// 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.IO; + using System.Linq; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class MspBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var messaging = context.ServiceProvider.GetService(); + + var backendHelper = context.ServiceProvider.GetService(); + + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + // Create transforms named in patch transforms. + IEnumerable patchTransforms; + { + var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); + patchTransforms = command.Execute(); + } + + // Enhance the intermediate by attaching the created patch transforms. + IEnumerable subStorages; + { + var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms); + subStorages = command.Execute(); + } + + // Create WindowsInstallerData with patch metdata and transforms as sub-storages + // Create MSP from WindowsInstallerData + IBindResult result = null; + try + { + var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); + result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; + } + catch + { + result?.Dispose(); + throw; + } + } + + public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); + + public bool Inscribe(IInscribeContext context) => throw new NotImplementedException(); + + public Intermediate Unbind(IUnbindContext context) + { +#if TODO_PATCHING + Output patch; + + // patch files are essentially database files (use a special flag to let the API know its a patch file) + try + { + using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + { + var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false); + patch = unbindCommand.Execute(); + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath)); + } + + throw; + } + + // retrieve the transforms (they are in substorages) + using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite)) + { + Table summaryInformationTable = patch.Tables["_SummaryInformation"]; + foreach (Row row in summaryInformationTable.Rows) + { + if (8 == (int)row[0]) // PID_LASTAUTHOR + { + string value = (string)row[1]; + + foreach (string decoratedSubStorageName in value.Split(';')) + { + string subStorageName = decoratedSubStorageName.Substring(1); + string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); + + // ensure the parent directory exists + Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); + + // copy the substorage to a new storage for the transform file + using (Storage subStorage = storage.OpenStorage(subStorageName)) + { + using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) + { + subStorage.CopyTo(transformStorage); + } + } + + // unbind the transform + var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder); + var transform = unbindCommand.Execute(); + + patch.SubStorages.Add(new SubStorage(subStorageName, transform)); + } + + break; + } + } + } + + // extract the files from the cabinets + // TODO: use per-transform export paths for support of multi-product patches + if (null != context.ExportBasePath && !context.SuppressExtractCabinets) + { + using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + { + foreach (SubStorage subStorage in patch.SubStorages) + { + // only patch transforms should carry files + if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) + { + var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder); + extractCommand.Execute(); + } + } + } + } + + return patch; +#endif + throw new NotImplementedException(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs new file mode 100644 index 00000000..a6d86c10 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.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.WindowsInstaller +{ + using System; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class MstBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { +#if TODO_PATCHING + 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()); +#endif + throw new NotImplementedException(); + } + + public IDecompileResult Decompile(IDecompileContext context) + { + throw new NotImplementedException(); + } + + public bool Inscribe(IInscribeContext context) + { + throw new NotImplementedException(); + } + + public Intermediate Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} \ No newline at end of file diff --git a/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs b/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs new file mode 100644 index 00000000..ad7764bc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs @@ -0,0 +1,71 @@ +// 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 WixToolset.Data.WindowsInstaller; + + /// + /// A dictionary of rows. Unlike the RowIndexedList this + /// will throw when multiple rows with the same key are added. + /// + internal sealed class RowDictionary : Dictionary where T : Row + { + /// + /// Creates an empty . + /// + public RowDictionary() + : base(StringComparer.InvariantCulture) + { + } + + /// + /// Creates and populates a with the rows from the given . + /// + /// The table to index. + /// + /// Rows added to the index are not automatically added to the given . + /// + public RowDictionary(Table table) + : this() + { + if (null != table) + { + foreach (T row in table.Rows) + { + this.Add(row); + } + } + } + + /// + /// Adds a row to the dictionary using the row key. + /// + /// Row to add to the dictionary. + public void Add(T row) + { + this.Add(row.GetKey(), row); + } + + /// + /// Gets the row by integer key. + /// + /// Integer key to look up. + /// Row or null if key is not found. + public T Get(int key) + { + return this.Get(key.ToString()); + } + + /// + /// Gets the row by string key. + /// + /// String key to look up. + /// Row or null if key is not found. + public T Get(string key) + { + return this.TryGetValue(key, out var result) ? result : null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs new file mode 100644 index 00000000..8f52bed9 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.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.Core.WindowsInstaller.Unbind +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + + internal class ExtractCabinetsCommand + { + public ExtractCabinetsCommand(WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false) + { + this.Output = output; + this.Database = database; + this.InputFilePath = inputFilePath; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + this.TreatOutputAsModule = treatOutputAsModule; + } + + public string[] ExtractedFiles { get; private set; } + + private WindowsInstallerData Output { get; } + + private Database Database { get; } + + private string InputFilePath { get; } + + private string ExportBasePath { get; } + + private string IntermediateFolder { get; } + + public bool TreatOutputAsModule { get; } + + public void Execute() + { + var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); + var cabinetFiles = new List(); + var embeddedCabinets = new SortedList(); + + // index all of the cabinet files + if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) + { + 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 (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) + { + foreach (int diskId in embeddedCabinets.Keys) + { + using (var record = new Record(1)) + { + record.SetString(1, (string)embeddedCabinets[diskId]); + streamsView.Execute(record); + } + + using (var record = streamsView.Fetch()) + { + if (null != record) + { + // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, + // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work + var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); + + // ensure the parent directory exists + Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); + + using (var fs = File.Create(cabinetFile)) + { + int bytesRead; + var 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) + { + // ensure the directory exists or extraction will fail + Directory.CreateDirectory(this.ExportBasePath); + + foreach (var cabinetFile in cabinetFiles) + { + try + { + var cabinet = new Cabinet(cabinetFile); + this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); + } + catch (FileNotFoundException) + { + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); + } + } + } + else + { + this.ExtractedFiles = new string[0]; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs new file mode 100644 index 00000000..b510690e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -0,0 +1,789 @@ +// 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.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class UnbindDatabaseCommand + { + private List exportedFiles; + + public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + 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 = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + } + + public IMessaging Messaging { get; } + + public IBackendHelper BackendHelper { 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; } + + public IEnumerable ExportedFiles => this.exportedFiles; + + private int SectionCount { get; set; } + + public WindowsInstallerData Execute() + { + this.exportedFiles = new List(); + + string modularizationGuid = null; + var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); + View validationView = null; + + // set the output type + output.Type = this.OutputType; + + Directory.CreateDirectory(this.IntermediateFolder); + + // get the codepage + this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); + using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) + { + string line; + + while (null != (line = sr.ReadLine())) + { + var 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 (var summaryInformation = new SummaryInformation(this.Database)) + { + var table = new Table(this.TableDefinitions["_SummaryInformation"]); + + for (var i = 1; 19 >= i; i++) + { + var value = summaryInformation.GetProperty(i); + + if (0 < value.Length) + { + var 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 (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) + { + foreach (var tableRecord in tablesView.Records) + { + var tableName = tableRecord.GetString(1); + + using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) + { + var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); + var table = new Table(tableDefinition); + + foreach (var rowRecord in tableView.Records) + { + var recordCount = rowRecord.GetFieldCount(); + var row = table.CreateRow(output.SourceLineNumbers); + + for (var 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: + var success = false; + var intValue = rowRecord.GetInteger(i + 1); + if (row.Fields[i].Column.IsLocalizable) + { + success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); + } + else + { + success = row.BestEffortSetField(i, intValue); + } + + if (!success) + { + this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); + } + break; + case ColumnType.Object: + var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; + + if (null != this.ExportBasePath) + { + var 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 (var fs = System.IO.File.Create(sourceFile)) + { + int bytesRead; + var buffer = new byte[512]; + + while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + this.exportedFiles.Add(sourceFile); + } + + row[i] = sourceFile; + break; + default: + var 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) + { + var modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); + + if (null == modularizationGuid) + { + var match = modularization.Match(value); + 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 + // TODO: Evaluate this requirement. + //var matches = Common.WixVariableRegex.Matches(value); + //for (var 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) + { + var table = output.Tables["_SummaryInformation"]; + + foreach (var row in table.Rows) + { + if (9 == (int)row[0]) // PID_REVNUMBER + { + row[1] = modularizationGuid; + } + } + } + + if (this.IsAdminImage) + { + this.GenerateWixFileTable(this.DatabasePath, output); + this.GenerateSectionIds(output); + } + + return output; + } + + private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) + { + // Use our table definitions whenever possible since they will be used when compiling the source code anyway. + // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes. + if (this.TableDefinitions.Contains(tableName)) + { + return this.TableDefinitions[tableName]; + } + + ColumnDefinition[] columns; + using (Record columnNameRecord = tableView.GetColumnNames(), + columnTypeRecord = tableView.GetColumnTypes()) + { + // index the primary keys + var tablePrimaryKeys = new HashSet(); + using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName)) + { + var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); + + for (var i = 1; i <= primaryKeysFieldCount; i++) + { + tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); + } + } + + var columnCount = columnNameRecord.GetFieldCount(); + columns = new ColumnDefinition[columnCount]; + for (var i = 1; i <= columnCount; i++) + { + var columnName = columnNameRecord.GetString(i); + var idtType = columnTypeRecord.GetString(i); + + ColumnType columnType; + int length; + bool nullable; + + var columnCategory = ColumnCategory.Unknown; + var columnModularizeType = ColumnModularizeType.None; + var primary = tablePrimaryKeys.Contains(columnName); + int? minValue = null; + int? maxValue = null; + string keyTable = null; + int? keyColumn = null; + 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 (var validationRecord = new Record(2)) + { + validationRecord.SetString(1, tableName); + validationRecord.SetString(2, columnName); + + validationView.Execute(validationRecord); + } + + using (var validationRecord = validationView.Fetch()) + { + if (null != validationRecord) + { + var validationNullable = validationRecord.GetString(3); + minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); + maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); + keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); + keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); + category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); + set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); + description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); + + // 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[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true); + } + } + + return new TableDefinition(tableName, null, columns, false); + } + + /// + /// Generates the WixFile table based on a path to an admin image msi and an Output. + /// + /// The path to the msi database file in an admin image. + /// The Output that represents the msi database. + private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output) + { + throw new NotImplementedException(); +#if TODO_FIX_UNBINDING_FILES + var adminRootPath = Path.GetDirectoryName(databaseFile); + + var componentDirectoryIndex = new Hashtable(); + var componentTable = output.Tables["Component"]; + foreach (var row in componentTable.Rows) + { + componentDirectoryIndex.Add(row[0], row[2]); + } + + // Index full source paths for all directories + var directoryDirectoryParentIndex = new Hashtable(); + var directoryFullPathIndex = new Hashtable(); + var directorySourceNameIndex = new Hashtable(); + var directoryTable = output.Tables["Directory"]; + foreach (var row in directoryTable.Rows) + { + directoryDirectoryParentIndex.Add(row[0], row[1]); + if (null == row[1]) + { + directoryFullPathIndex.Add(row[0], adminRootPath); + } + else + { + directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); + } + } + + foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) + { + if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) + { + this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + } + } + + var fileTable = output.Tables["File"]; + var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); + foreach (var row in fileTable.Rows) + { + var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); + wixFileRow.File = (string)row[0]; + wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; + wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); + + if (!File.Exists(wixFileRow.Source)) + { + throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); + } + + wixFileTable.Rows.Add(wixFileRow); + } +#endif + } + + /// + /// 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) + { + var parent = (string)directoryDirectoryParentIndex[directory]; + var sourceName = (string)directorySourceNameIndex[directory]; + + string parentFullPath; + if (directoryFullPathIndex.ContainsKey(parent)) + { + parentFullPath = (string)directoryFullPathIndex[parent]; + } + else + { + parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + } + + if (null == sourceName) + { + sourceName = String.Empty; + } + + var 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 string GetAdminSourceName(string value) + { + string name = null; + string[] names; + string shortname = null; + string shortsourcename = null; + string sourcename = null; + + names = this.BackendHelper.SplitMsiFileName(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(WindowsInstallerData output) + { + // First assign and index section ids for the tables that are in their own sections. + this.AssignSectionIdsToTable(output.Tables["Binary"], 0); + var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0); + var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0); + this.AssignSectionIdsToTable(output.Tables["Directory"], 0); + var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0); + this.AssignSectionIdsToTable(output.Tables["Icon"], 0); + var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); + this.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. + var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); + var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); + var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); + var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); + var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); + var 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 (var 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 (var row in table.Rows) + { + var lockObject = (string)row[0]; + var 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) + { + var hashtable = new Hashtable(); + if (null != table) + { + foreach (var row in table.Rows) + { + row.SectionId = this.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 (var 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) + { + var newHashTable = new Hashtable(); + if (null != table) + { + foreach (var 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/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs new file mode 100644 index 00000000..75ee6307 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs @@ -0,0 +1,55 @@ +// 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.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Core.Native.Msi; + + internal class UnbindMsiOrMsmCommand + { + public UnbindMsiOrMsmCommand(IUnbindContext context) + { + this.Context = context; + } + + public IUnbindContext Context { get; } + + public Intermediate Execute() + { +#if TODO_PATCHING + 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; +#endif + throw new NotImplementedException(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs new file mode 100644 index 00000000..f40aed4e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs @@ -0,0 +1,309 @@ +// 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 WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class UnbindTransformCommand + { + public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.TransformFile = transformFile; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + + this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private string TransformFile { get; } + + private string ExportBasePath { get; } + + private string IntermediateFolder { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string EmptyFile { get; set; } + + public WindowsInstallerData Execute() + { + var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); + transform.Type = OutputType.Transform; + + // get the summary information table + using (var summaryInformation = new SummaryInformation(this.TransformFile)) + { + var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + + for (var i = 1; 19 >= i; i++) + { + var value = summaryInformation.GetProperty(i); + + if (0 < value.Length) + { + var row = table.CreateRow(transform.SourceLineNumbers); + row[0] = i; + row[1] = value; + } + } + } + + // create a schema msi which hopefully matches the table schemas in the transform + var schemaOutput = new WindowsInstallerData(null); + var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); + foreach (var tableDefinition in this.TableDefinitions) + { + // skip unreal tables and the Patch table + if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) + { + schemaOutput.EnsureTable(tableDefinition); + } + } + + var addedRows = new Dictionary(); + Table transformViewTable; + + // Bind the schema msi. + this.GenerateDatabase(schemaOutput, msiDatabaseFile); + + // apply the transform to the database and retrieve the modifications + using (var 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, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + var transformViewOutput = unbindCommand.Execute(); + + // index the added and possibly modified rows (added rows may also appears as modified rows) + transformViewTable = transformViewOutput.Tables["_TransformView"]; + var modifiedRows = new Hashtable(); + foreach (var row in transformViewTable.Rows) + { + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; + + if ("INSERT" == columnName) + { + var index = String.Concat(tableName, ':', primaryKeys); + + addedRows.Add(index, null); + } + else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row + { + var 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) + { + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; + + var index = String.Concat(tableName, ':', primaryKeys); + + // ignore information for added rows + if (!addedRows.ContainsKey(index)) + { + var 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 (var 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(ErrorMessages.TransformSchemaMismatch()); + } + } + + // unbind the database + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + var output = unbindCommand.Execute(); + + // index all the rows to easily find modified rows + var rows = new Dictionary(); + foreach (var table in output.Tables) + { + foreach (var row in table.Rows) + { + rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); + } + } + + // process the _TransformView rows into transform rows + foreach (var row in transformViewTable.Rows) + { + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; + + var table = transform.EnsureTable(this.TableDefinitions[tableName]); + + if ("CREATE" == columnName) // added table + { + table.Operation = TableOperation.Add; + } + else if ("DELETE" == columnName) // deleted row + { + var 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 + { + var index = String.Concat(tableName, ':', primaryKeys); + var addedRow = rows[index]; + addedRow.Operation = RowOperation.Add; + table.Rows.Add(addedRow); + } + else if (null != primaryKeys) // modified row + { + var 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.ContainsKey(index)) + { + var modifiedRow = rows[index]; + + // mark the field as modified + var indexOfModifiedValue = -1; + for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++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 + { + var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); + column.Added = true; + } + } + } + + return transform; + } + + /// + /// 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) + { + var row = table.CreateRow(null); + + var primaryKeyParts = primaryKeys.Split('\t'); + var primaryKeyPartIndex = 0; + + for (var i = 0; i < table.Definition.Columns.Length; i++) + { + var 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.Combine(this.IntermediateFolder, ".empty"); + using (var fileStream = File.Create(this.EmptyFile)) + { + } + } + + row[i] = this.EmptyFile; + } + else + { + row[i] = "1"; + } + } + } + + return row; + } + + private void GenerateDatabase(WindowsInstallerData output, string databaseFile) + { + var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); + command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs new file mode 100644 index 00000000..0c15ad05 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.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.WindowsInstaller +{ + using WixToolset.Data; + + internal static class WindowsInstallerBackendErrors + { + //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers) + //{ + // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2); + //} + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + // ReplaceThisWithTheFirstError = 7500, + } // last available is 7999. 8000 is BurnBackendErrors. + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs new file mode 100644 index 00000000..f72acb21 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs @@ -0,0 +1,51 @@ +// 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, 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 "package": + 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/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs new file mode 100644 index 00000000..d0986a4d --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.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.WindowsInstaller +{ + using WixToolset.Data; + + internal static class WindowsInstallerBackendWarnings + { + //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers) + //{ + // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2); + //} + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + // ReplaceThisWithTheFirstWarning = 7100, + } // last available is 7499. 7500 is WindowsInstallerBackendErrors. + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs new file mode 100644 index 00000000..7b12fc8c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs @@ -0,0 +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.Core.WindowsInstaller +{ + using System; + using WixToolset.Extensibility; + + internal class WindowsInstallerExtensionFactory : IExtensionFactory + { + public bool TryCreateExtension(Type extensionType, out object extension) + { + extension = null; + + if (extensionType == typeof(IBackendFactory)) + { + extension = new WindowsInstallerBackendFactory(); + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj new file mode 100644 index 00000000..b08f337f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj @@ -0,0 +1,30 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Core Windows Installer + WiX Toolset Core Windows Installer + embedded + true + true + + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..e686fa49 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs @@ -0,0 +1,42 @@ +// 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 WixToolset.Core.WindowsInstaller.ExtensibilityServices; + using WixToolset.Extensibility.Services; + + /// + /// Extensions methods for adding WindowsInstaller services. + /// + public static class WixToolsetCoreServiceProviderExtensions + { + /// + /// Adds WindowsInstaller services. + /// + /// + /// + public static IWixToolsetCoreServiceProvider AddWindowsInstallerBackend(this IWixToolsetCoreServiceProvider coreProvider) + { + AddServices(coreProvider); + + var extensionManager = coreProvider.GetService(); + extensionManager.Add(typeof(WindowsInstallerExtensionFactory).Assembly); + + return coreProvider; + } + + private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) + { + // Singletons. + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper(provider))); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } + } +} diff --git a/src/wix/WixToolset.Core.sln b/src/wix/WixToolset.Core.sln new file mode 100644 index 00000000..523c960e --- /dev/null +++ b/src/wix/WixToolset.Core.sln @@ -0,0 +1,156 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2009 +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 +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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.ExtensionCache", "src\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj", "{A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1284331E-BC6C-426D-AAAF-140C0174F875}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Extension", "src\test\Example.Extension\Example.Extension.csproj", "{C66C2503-C671-4230-8B48-1D93A8532A28}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegration", "src\test\WixToolsetTest.CoreIntegration\WixToolsetTest.CoreIntegration.csproj", "{E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Burn", "src\test\WixToolsetTest.Core.Burn\WixToolsetTest.Core.Burn.csproj", "{DF63F589-028E-45A1-A212-948FACF1FDCD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}" +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 + {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 + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.Build.0 = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.Build.0 = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.Build.0 = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.ActiveCfg = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.Build.0 = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.ActiveCfg = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.Build.0 = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.ActiveCfg = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.Build.0 = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.ActiveCfg = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.Build.0 = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.Build.0 = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.ActiveCfg = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.Build.0 = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.ActiveCfg = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.Build.0 = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.Build.0 = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.Build.0 = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.Build.0 = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.ActiveCfg = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.Build.0 = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.ActiveCfg = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.Build.0 = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.Build.0 = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.Build.0 = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.Build.0 = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.ActiveCfg = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.Build.0 = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.ActiveCfg = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.Build.0 = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.ActiveCfg = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.Build.0 = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.ActiveCfg = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.Build.0 = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.Build.0 = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.ActiveCfg = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {DF63F589-028E-45A1-A212-948FACF1FDCD} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA} + EndGlobalSection +EndGlobal diff --git a/src/wix/WixToolset.Core.v3.ncrunchsolution b/src/wix/WixToolset.Core.v3.ncrunchsolution new file mode 100644 index 00000000..10420ac9 --- /dev/null +++ b/src/wix/WixToolset.Core.v3.ncrunchsolution @@ -0,0 +1,6 @@ + + + True + True + + \ No newline at end of file diff --git a/src/wix/WixToolset.Core/Bind/DelayedField.cs b/src/wix/WixToolset.Core/Bind/DelayedField.cs new file mode 100644 index 00000000..25641516 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/DelayedField.cs @@ -0,0 +1,35 @@ +// 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 WixToolset.Data; + using WixToolset.Extensibility.Data; + + /// + /// Holds a symbol and field that contain binder variables, which need to be resolved + /// later, once the files have been resolved. + /// + internal class DelayedField : IDelayedField + { + /// + /// Creates a delayed field. + /// + /// Symbol for the field. + /// Field needing further resolution. + public DelayedField(IntermediateSymbol symbol, IntermediateField field) + { + this.Symbol = symbol; + this.Field = field; + } + + /// + /// The row containing the field. + /// + public IntermediateSymbol Symbol { get; } + + /// + /// The field needing further resolving. + /// + public IntermediateField Field { get; } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs new file mode 100644 index 00000000..b27cdfee --- /dev/null +++ b/src/wix/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.Data; + + internal class ExpectedExtractFile : IExpectedExtractFile + { + public Uri Uri { get; set; } + + public string EmbeddedFileId { get; set; } + + public string OutputPath { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs new file mode 100644 index 00000000..a0798e62 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + + /// + /// Internal helper class used to extract embedded files. + /// + internal class ExtractEmbeddedFiles + { + private readonly Dictionary> filesWithEmbeddedFiles = new Dictionary>(); + + 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. + /// + /// Uri to file containing the embedded files. + /// Id of the embedded file to extract. + /// Folder where extracted files should be placed. + /// The extract path for the embedded file. + public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder) + { + // If the uri to the file that contains the embedded file does not already have embedded files + // being extracted, create the dictionary to track that. + if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) + { + extracts = new SortedList(StringComparer.OrdinalIgnoreCase); + this.filesWithEmbeddedFiles.Add(uri, extracts); + } + + // If the embedded file is not already tracked in the dictionary of extracts, add it. + if (!extracts.TryGetValue(embeddedFileId, out var extractPath)) + { + var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); + var unique = this.HashUri(uri.AbsoluteUri); + var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); + + extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); + extracts.Add(embeddedFileId, extractPath); + } + + return extractPath; + } + + public IReadOnlyList GetExpectedEmbeddedFiles() + { + var files = new List(); + + foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) + { + foreach (var extracts in uriWithExtracts.Value) + { + files.Add(new ExpectedExtractFile + { + Uri = uriWithExtracts.Key, + EmbeddedFileId = extracts.Key, + OutputPath = extracts.Value, + }); + } + } + + return files; + } + + public IEnumerable GetExtractFilesForUri(Uri uri) + { + if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) + { + extracts = new SortedList(StringComparer.OrdinalIgnoreCase); + } + + return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value }); + } + + private string HashUri(string uri) + { + using (SHA1 sha1 = new SHA1CryptoServiceProvider()) + { + var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); + return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs new file mode 100644 index 00000000..ec2d8896 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ExtractEmbeddedFilesCommand + { + public ExtractEmbeddedFilesCommand(IBackendHelper backendHelper, IEnumerable embeddedFiles) + { + this.BackendHelper = backendHelper; + this.FilesWithEmbeddedFiles = embeddedFiles; + } + + public IReadOnlyList TrackedFiles { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IEnumerable FilesWithEmbeddedFiles { get; } + + public void Execute() + { + var trackedFiles = new List(); + var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); + + foreach (var expectedEmbeddedFileByUri in group) + { + var baseUri = expectedEmbeddedFileByUri.Key; + + using (var wixout = WixOutput.Read(baseUri)) + { + var uniqueIds = new SortedSet(StringComparer.OrdinalIgnoreCase); + + foreach (var embeddedFile in expectedEmbeddedFileByUri) + { + if (uniqueIds.Add(embeddedFile.EmbeddedFileId)) + { + wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath); + trackedFiles.Add(this.BackendHelper.TrackFile(embeddedFile.OutputPath, TrackedFileType.Temporary)); + } + } + } + } + + this.TrackedFiles = trackedFiles; + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/FileResolver.cs b/src/wix/WixToolset.Core/Bind/FileResolver.cs new file mode 100644 index 00000000..eb878239 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/FileResolver.cs @@ -0,0 +1,207 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class FileResolver + { + private const string BindPathOpenString = "!(bindpath."; + + private FileResolver(IEnumerable bindPaths) + { + this.BindPaths = (bindPaths ?? Array.Empty()).ToLookup(b => b.Stage); + this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); + this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); + } + + public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) + { + this.ResolverExtensions = extensions ?? Array.Empty(); + } + + public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) + { + this.LibrarianExtensions = extensions ?? Array.Empty(); + } + + private ILookup BindPaths { get; } + + public bool RebaseTarget { get; } + + public bool RebaseUpdated { get; } + + private IEnumerable ResolverExtensions { get; } + + private IEnumerable LibrarianExtensions { get; } + + public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source) + { + var checkedPaths = new List(); + + foreach (var extension in this.LibrarianExtensions) + { + var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); + } + + /// + /// Resolves the source path of a file using binder extensions. + /// + /// Original source value. + /// Optional type of source file being resolved. + /// Optional source line of source file being resolved. + /// The binding stage used to determine what collection of bind paths will be used + /// Optional collection of paths already checked. + /// Should return a valid path for the stream to be imported. + public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable alreadyCheckedPaths = null) + { + var checkedPaths = new List(); + + if (alreadyCheckedPaths != null) + { + checkedPaths.AddRange(alreadyCheckedPaths); + } + + foreach (var extension in this.ResolverExtensions) + { + var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); + } + + private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List checkedPaths) + { + string resolved = null; + + // If the file exists, we're good to go. + checkedPaths.Add(source); + if (CheckFileExists(source)) + { + resolved = source; + } + else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. + { + resolved = null; + } + else // not a rooted path so let's try applying all the different source resolution options. + { + var bindName = String.Empty; + var path = source; + var pathWithoutSourceDir = String.Empty; + + if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) + { + var closeParen = source.IndexOf(')', BindPathOpenString.Length); + + if (-1 != closeParen) + { + bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); + path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. + path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. + } + } + else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) + { + pathWithoutSourceDir = path.Substring(10); + } + + var bindPaths = this.BindPaths[bindStage]; + + foreach (var bindPath in bindPaths) + { + if (String.IsNullOrEmpty(bindName)) + { + if (String.IsNullOrEmpty(bindPath.Name)) + { + if (!String.IsNullOrEmpty(pathWithoutSourceDir)) + { + var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + + if (String.IsNullOrEmpty(resolved)) + { + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + } + } + else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) + { + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + + if (!String.IsNullOrEmpty(resolved)) + { + break; + } + } + } + + if (null == resolved) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); + } + + return resolved; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs new file mode 100644 index 00000000..4ad8f764 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs @@ -0,0 +1,164 @@ +// 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.Globalization; + using System.Text; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Resolves the fields which had variables that needed to be resolved after the file information + /// was loaded. + /// + internal class ResolveDelayedFieldsCommand + { + /// + /// Resolve delayed fields. + /// + /// + /// The fields which had resolution delayed. + /// The cached variable values used when resolving delayed fields. + public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable delayedFields, Dictionary variableCache) + { + this.Messaging = messaging; + this.DelayedFields = delayedFields; + this.VariableCache = variableCache; + } + + private IMessaging Messaging { get; } + + private IEnumerable DelayedFields { get;} + + private IDictionary VariableCache { get; } + + public void Execute() + { + var deferredFields = new List(); + + foreach (var delayedField in this.DelayedFields) + { + try + { + var propertySymbol = delayedField.Symbol; + + // process properties first in case they refer to other binder variables + if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property) + { + var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString()); + + // update the variable cache with the new value + var key = String.Concat("property.", propertySymbol.Id.Id); + this.VariableCache[key] = value; + + // update the field data + delayedField.Field.Set(value); + } + else + { + deferredFields.Add(delayedField); + } + } + catch (WixException we) + { + this.Messaging.Write(we.Error); + continue; + } + } + + // add specialization for ProductVersion fields + var keyProductVersion = "property.ProductVersion"; + if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion)) + { + // Don't add the variable if it already exists (developer defined a property with the same name). + var fieldKey = String.Concat(keyProductVersion, ".Major"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); + } + + fieldKey = String.Concat(keyProductVersion, ".Minor"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture); + } + + fieldKey = String.Concat(keyProductVersion, ".Build"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture); + } + + fieldKey = String.Concat(keyProductVersion, ".Revision"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture); + } + } + + // process the remaining fields in case they refer to property binder variables + foreach (var delayedField in deferredFields) + { + try + { + var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString()); + delayedField.Field.Set(value); + } + catch (WixException we) + { + this.Messaging.Write(we.Error); + } + } + } + + private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value) + { + var start = 0; + + while (Common.TryParseWixVariable(value, start, out var parsed)) + { + if (parsed.Namespace == "bind") + { + var key = String.Concat(parsed.Name, ".", parsed.Scope); + + if (!this.VariableCache.TryGetValue(key, out var resolvedValue)) + { + resolvedValue = parsed.DefaultValue; + } + + // insert the resolved value if it was found or display an error + if (null != resolvedValue) + { + if (parsed.Index == 0 && parsed.Length == value.Length) + { + value = resolvedValue; + } + else + { + var sb = new StringBuilder(value); + sb.Remove(parsed.Index, parsed.Length); + sb.Insert(parsed.Index, resolvedValue); + value = sb.ToString(); + } + + start = parsed.Index; + } + else + { + this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); + break; + } + } + else + { + start = parsed.Index + parsed.Length; + } + } + + return value; + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs new file mode 100644 index 00000000..794208e5 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -0,0 +1,276 @@ +// 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.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Resolve source fields in the tables included in the output + /// + internal class ResolveFieldsCommand + { + public IMessaging Messaging { private get; set; } + + public bool BuildingPatch { private get; set; } + + public IVariableResolver VariableResolver { private get; set; } + + public IEnumerable BindPaths { private get; set; } + + public IEnumerable Extensions { private get; set; } + + public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } + + public string IntermediateFolder { private get; set; } + + public Intermediate Intermediate { private get; set; } + + public bool SupportDelayedResolution { private get; set; } + + public bool AllowUnresolvedVariables { private get; set; } + + public IReadOnlyCollection DelayedFields { get; private set; } + + public void Execute() + { + var delayedFields = this.SupportDelayedResolution ? new List() : null; + + var fileResolver = new FileResolver(this.BindPaths, this.Extensions); + + // Build the column lookup only when needed. + Dictionary customColumnsById = null; + + foreach (var sections in this.Intermediate.Sections) + { + foreach (var symbol in sections.Symbols) + { + foreach (var field in symbol.Fields) + { + if (field.IsNull()) + { + continue; + } + + var fieldType = field.Type; + + // Custom table cells require an extra look up to the column definition as the + // cell's data type is always a string (because strings can store anything) but + // the column definition may be more specific. + if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) + { + // We only care about the Data in a CustomTable cell. + if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) + { + continue; + } + + if (customColumnsById == null) + { + customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType()).ToDictionary(t => t.Id.Id); + } + + if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) + { + fieldType = customColumn.Type; + } + } + + // Check to make sure we're in a scenario where we can handle variable resolution. + if (null != delayedFields) + { + // resolve localization and wix variables + if (fieldType == IntermediateFieldType.String) + { + var original = field.AsString(); + if (!String.IsNullOrEmpty(original)) + { + var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); + if (resolution.UpdatedValue) + { + field.Set(resolution.Value); + } + + if (resolution.DelayedResolve) + { + delayedFields.Add(new DelayedField(symbol, field)); + } + } + } + } + + // Move to next symbol if we've hit an error resolving variables. + if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. + { + continue; + } + + // Resolve file paths + if (fieldType == IntermediateFieldType.Path) + { + this.ResolvePathField(fileResolver, symbol, field); + +#if TODO_PATCHING + if (null != objectField.PreviousData) + { + objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); + + if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. + { + // file is compressed in a cabinet (and not modified above) + if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) + { + // when loading transforms from disk, PreviousBaseUri may not have been set + if (null == objectField.PreviousBaseUri) + { + objectField.PreviousBaseUri = objectField.BaseUri; + } + + string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); + + // set the path to the file once its extracted from the cabinet + objectField.PreviousData = extractPath; + } + else if (null != objectField.PreviousData) // non-compressed file (or localized value) + { + try + { + if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) + { + // resolve the path to the file + objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal); + } + else + { + if (fileResolver.RebaseTarget) + { + // if -bt is used, it come here + // Try to use the original unresolved source from either target build or update build + // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here + // If target is old version and updated is new version, it uses unresolved path from updated build + // If both target and updated are of new versions, it uses unresolved path from target build + if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData) + { + objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData; + } + } + + // resolve the path to the file + objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target); + + } + } + catch (WixFileNotFoundException) + { + // display the error with source line information + Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData)); + } + } + } + } +#endif + } + } + } + } + + this.DelayedFields = delayedFields; + } + + private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) + { + var fieldValue = field.AsPath(); + var originalFieldPath = fieldValue.Path; + +#if TODO_PATCHING + // Skip file resolution if the file is to be deleted. + if (RowOperation.Delete == symbol.Operation) + { + continue; + } +#endif + + // If the file is embedded and if the previous value has a bind variable in the path + // which gets modified by resolving the previous value again then switch to that newly + // resolved path instead of using the embedded file. + if (fieldValue.Embed && field.PreviousValue != null) + { + var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false); + + if (resolution.UpdatedValue && !resolution.IsDefault) + { + fieldValue = new IntermediateFieldPathValue { Path = resolution.Value }; + } + } + + // If we're still using the embedded file. + if (fieldValue.Embed) + { + // Set the path to the embedded file once where it will be extracted. + var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(fieldValue.BaseUri, fieldValue.Path, this.IntermediateFolder); + + field.Set(extractPath); + } + else if (fieldValue.Path != null) + { + try + { + var resolvedPath = fieldValue.Path; + + if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) + { +#if TODO_PATCHING + // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file + if (null == objectField.UnresolvedData) + { + objectField.UnresolvedData = (string)objectField.Data; + } +#endif + resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); + } + else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) + { + resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); + } +#if TODO_PATCHING + else // Re-base binding path scenario caused by pyro.exe -bt -bu + { + // by default, use the resolved Data for file lookup + string filePathToResolve = (string)objectField.Data; + + // if -bu is used in pyro command, this condition holds true and the tool + // will use pre-resolved source for new wixpdb file + if (fileResolver.RebaseUpdated) + { + // try to use the unResolved Source if it exists. + // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll + // Old version of winpdb file does not contain this attribute and the value is null. + if (null != objectField.UnresolvedData) + { + filePathToResolve = objectField.UnresolvedData; + } + } + + objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated); + } +#endif + + if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) + { + field.Set(resolvedPath); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs new file mode 100644 index 00000000..b3b74fbc --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs @@ -0,0 +1,196 @@ +// 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 WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class TransferFilesCommand + { + public TransferFilesCommand(IMessaging messaging, IEnumerable extensions, IEnumerable fileTransfers, bool resetAcls) + { + this.Extensions = extensions; + this.Messaging = messaging; + this.FileTransfers = fileTransfers; + this.ResetAcls = resetAcls; + } + + private IMessaging Messaging { get; } + + private IEnumerable Extensions { get; } + + private IEnumerable FileTransfers { get; } + + private bool ResetAcls { get; } + + public void Execute() + { + var destinationFiles = new List(); + + foreach (var fileTransfer in this.FileTransfers) + { + // If the source and destination are identical, then there's nothing to do here + if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) + { + fileTransfer.Redundant = true; + continue; + } + + var retry = false; + do + { + try + { + if (fileTransfer.Move) + { + this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination)); + this.MoveFile(fileTransfer.Source, fileTransfer.Destination); + } + else + { + this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination)); + this.CopyFile(fileTransfer.Source, fileTransfer.Destination); + } + + retry = false; + destinationFiles.Add(fileTransfer.Destination); + } + catch (FileNotFoundException e) + { + throw new WixException(ErrorMessages.FileNotFound(fileTransfer.SourceLineNumbers, e.FileName)); + } + catch (DirectoryNotFoundException) + { + // if we already retried, give up + if (retry) + { + throw; + } + + var directory = Path.GetDirectoryName(fileTransfer.Destination); + this.Messaging.Write(VerboseMessages.CreateDirectory(directory)); + Directory.CreateDirectory(directory); + retry = true; + } + catch (UnauthorizedAccessException) + { + // if we already retried, give up + if (retry) + { + throw; + } + + if (File.Exists(fileTransfer.Destination)) + { + this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); + + // try to ensure the file is not read-only + var attributes = File.GetAttributes(fileTransfer.Destination); + try + { + File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); + } + catch (ArgumentException) // thrown for unauthorized access errors + { + throw new WixException(ErrorMessages.UnauthorizedAccess(fileTransfer.Destination)); + } + + // try to delete the file + try + { + File.Delete(fileTransfer.Destination); + } + catch (IOException) + { + throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); + } + + retry = true; + } + else // no idea what just happened, bail + { + throw; + } + } + catch (IOException) + { + // if we already retried, give up + if (retry) + { + throw; + } + + if (File.Exists(fileTransfer.Destination)) + { + this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); + + // ensure the file is not read-only, then delete it + var attributes = File.GetAttributes(fileTransfer.Destination); + File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); + try + { + File.Delete(fileTransfer.Destination); + } + catch (IOException) + { + throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); + } + + retry = true; + } + else // no idea what just happened, bail + { + throw; + } + } + } while (retry); + } + + // Finally, if directed then reset remove ACLs that may may have been picked up + // during the file transfer process. + if (this.ResetAcls && 0 < destinationFiles.Count) + { + try + { + FileSystem.ResetAcls(destinationFiles); + } + catch (Exception e) + { + this.Messaging.Write(WarningMessages.UnableToResetAcls(e.Message)); + } + } + } + + private void CopyFile(string source, string destination) + { + foreach (var extension in this.Extensions) + { + if (extension.CopyFile(source, destination)) + { + return; + } + } + + FileSystem.CopyFile(source, destination, allowHardlink: true); + } + + private void MoveFile(string source, string destination) + { + foreach (var extension in this.Extensions) + { + if (extension.MoveFile(source, destination)) + { + return; + } + } + + FileSystem.MoveFile(source, destination); + } + } +} diff --git a/src/wix/WixToolset.Core/BindContext.cs b/src/wix/WixToolset.Core/BindContext.cs new file mode 100644 index 00000000..052382f1 --- /dev/null +++ b/src/wix/WixToolset.Core/BindContext.cs @@ -0,0 +1,65 @@ +// 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.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class BindContext : IBindContext + { + internal BindContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection BindPaths { get; set; } + + public string BurnStubPath { get; set; } + + public int CabbingThreadCount { get; set; } + + public string CabCachePath { get; set; } + + public CompressionLevel? DefaultCompressionLevel { get; set; } + + public IReadOnlyCollection DelayedFields { get; set; } + + public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection FileSystemExtensions { get; set; } + + public IReadOnlyCollection Ices { get; set; } + + public string IntermediateFolder { get; set; } + + public Intermediate IntermediateRepresentation { get; set; } + + public string OutputPath { get; set; } + + public PdbType PdbType { get; set; } + + public string PdbPath { get; set; } + + public int? ResolvedCodepage { get; set; } + + public int? ResolvedSummaryInformationCodepage { get; set; } + + public int? ResolvedLcid { get; set; } + + public IReadOnlyCollection SuppressIces { get; set; } + + public bool SuppressValidation { get; set; } + + public bool SuppressLayout { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/BindFileWithPath.cs b/src/wix/WixToolset.Core/BindFileWithPath.cs new file mode 100644 index 00000000..539600d3 --- /dev/null +++ b/src/wix/WixToolset.Core/BindFileWithPath.cs @@ -0,0 +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.Core +{ + using WixToolset.Extensibility.Data; + + /// + /// Bind file with its path. + /// + internal class BindFileWithPath : IBindFileWithPath + { + /// + /// Gets or sets the identifier of the file with this path. + /// + public string Id { get; set; } + + /// + /// Gets or sets the file path. + /// + public string Path { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/BindPath.cs b/src/wix/WixToolset.Core/BindPath.cs new file mode 100644 index 00000000..f70d5e36 --- /dev/null +++ b/src/wix/WixToolset.Core/BindPath.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 System.Diagnostics; + using WixToolset.Extensibility.Data; + + /// + /// Bind path representation. + /// + [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")] + internal class BindPath : IBindPath + { + public string Name { get; set; } + + public string Path { get; set; } + + public BindStage Stage { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/BindResult.cs b/src/wix/WixToolset.Core/BindResult.cs new file mode 100644 index 00000000..9785484c --- /dev/null +++ b/src/wix/WixToolset.Core/BindResult.cs @@ -0,0 +1,48 @@ +// 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.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class BindResult : IBindResult + { + private bool disposed; + + public IReadOnlyCollection FileTransfers { get; set; } + + public IReadOnlyCollection TrackedFiles { get; set; } + + public WixOutput Wixout { get; set; } + + #region IDisposable Support + /// + /// Disposes of the internal state of the file structure. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of the internsl state of the file structure. + /// + /// True if disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.Wixout?.Dispose(); + } + } + + this.disposed = true; + } + #endregion + } +} diff --git a/src/wix/WixToolset.Core/Binder.cs b/src/wix/WixToolset.Core/Binder.cs new file mode 100644 index 00000000..204ab6ee --- /dev/null +++ b/src/wix/WixToolset.Core/Binder.cs @@ -0,0 +1,96 @@ +// 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; + using System.Linq; + using System.Reflection; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Binder of the WiX toolset. + /// + internal class Binder : IBinder + { + internal Binder(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IBindResult Bind(IBindContext context) + { + // Prebind. + // + foreach (var extension in context.Extensions) + { + extension.PreBind(context); + } + + // Bind. + // + this.WriteBuildInfoSymbol(context.IntermediateRepresentation, context.OutputPath, context.PdbPath); + + var bindResult = this.BackendBind(context); + + if (bindResult != null) + { + // Postbind. + // + foreach (var extension in context.Extensions) + { + extension.PostBind(bindResult); + } + } + + return bindResult; + } + + private IBindResult BackendBind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendFactories = extensionManager.GetServices(); + + var entrySection = context.IntermediateRepresentation.Sections.First(); + + foreach (var factory in backendFactories) + { + if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, out var backend)) + { + var result = backend.Bind(context); + return result; + } + } + + // TODO: messaging that a backend could not be found to bind the output type? + + return null; + } + + private void WriteBuildInfoSymbol(Intermediate output, string outputFile, string outputPdbPath) + { + var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); + + var executingAssembly = Assembly.GetExecutingAssembly(); + var fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); + + var buildInfoSymbol = entrySection.AddSymbol(new WixBuildInfoSymbol() + { + WixVersion = fileVersion.FileVersion, + WixOutputFile = outputFile, + }); + + if (!String.IsNullOrEmpty(outputPdbPath)) + { + buildInfoSymbol.WixPdbFile = outputPdbPath; + } + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs new file mode 100644 index 00000000..5f618b81 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs @@ -0,0 +1,912 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BuildCommand : ICommandLineCommand + { + private readonly CommandLine commandLine; + + public BuildCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging); + } + + public bool ShowLogo => this.commandLine.ShowLogo; + + public bool StopParsing => this.commandLine.ShowHelp; + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IExtensionManager ExtensionManager { get; } + + private string IntermediateFolder { get; set; } + + private OutputType OutputType { get; set; } + + private List IncludeSearchPaths { get; set; } + + public string PdbFile { get; set; } + + public PdbType PdbType { get; set; } + + private Platform Platform { get; set; } + + private string OutputFile { get; set; } + + private CompressionLevel? DefaultCompressionLevel { get; set; } + + private string ContentsFile { get; set; } + + private string OutputsFile { get; set; } + + private string BuiltOutputsFile { get; set; } + + public Task ExecuteAsync(CancellationToken cancellationToken) + { + if (this.commandLine.ShowHelp) + { + Console.WriteLine("TODO: Show build command help"); + return Task.FromResult(-1); + } + + this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); + + this.OutputType = this.commandLine.CalculateOutputType(); + + this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths; + + this.PdbFile = this.commandLine.PdbFile; + + this.PdbType = this.commandLine.PdbType; + + this.Platform = this.commandLine.Platform; + + this.ContentsFile = this.commandLine.ContentsFile; + + this.OutputsFile = this.commandLine.OutputsFile; + + this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; + + this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel; + + var preprocessorVariables = this.commandLine.GatherPreprocessorVariables(); + + var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder); + + var filterCultures = this.commandLine.CalculateFilterCultures(); + + var creator = this.ServiceProvider.GetService(); + + this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl); + + this.OutputFile = this.commandLine.OutputFile; + + if (String.IsNullOrEmpty(this.OutputFile)) + { + if (codeFiles.Count == 1) + { + // If output type is unknown, the extension will be replaced with the right default based on output type. + this.OutputFile = Path.ChangeExtension(codeFiles[0].OutputPath, DefaultExtensionForOutputType(this.OutputType)); + } + else + { + this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput()); + } + } + + if (this.Messaging.EncounteredError) + { + return Task.FromResult(this.Messaging.LastErrorNumber); + } + + var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles, cancellationToken); + + var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables, cancellationToken); + + if (this.Messaging.EncounteredError) + { + return Task.FromResult(this.Messaging.LastErrorNumber); + } + + if (this.OutputType == OutputType.Library) + { + using (new IntermediateFieldContext("wix.lib")) + { + var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths, cancellationToken); + + if (!this.Messaging.EncounteredError) + { + wixlib.Save(this.OutputFile); + } + } + } + else + { + using (new IntermediateFieldContext("wix.link")) + { + if (wixipl == null) + { + wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator, cancellationToken); + } + + if (!this.Messaging.EncounteredError) + { + var outputExtension = Path.GetExtension(this.OutputFile); + if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension) + { + var entrySectionType = wixipl.Sections.Single().Type; + this.OutputFile = Path.ChangeExtension(this.OutputFile, DefaultExtensionForSectionType(entrySectionType)); + } + + if (this.OutputType == OutputType.IntermediatePostLink) + { + wixipl.Save(this.OutputFile); + } + else + { + using (new IntermediateFieldContext("wix.bind")) + { + this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, cancellationToken); + } + } + } + } + } + + return Task.FromResult(this.Messaging.LastErrorNumber); + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + return this.commandLine.TryParseArgument(argument, parser); + } + + private void EvaluateSourceFiles(IEnumerable sourceFiles, ISymbolDefinitionCreator creator, out List codeFiles, out Intermediate wixipl) + { + codeFiles = new List(); + + wixipl = null; + + foreach (var sourceFile in sourceFiles) + { + var extension = Path.GetExtension(sourceFile.SourcePath); + + if (wixipl != null || ".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase)) + { + codeFiles.Add(sourceFile); + } + else + { + try + { + wixipl = Intermediate.Load(sourceFile.SourcePath, creator); + } + catch (WixException) + { + // We'll assume anything that isn't a valid intermediate is source code to compile. + codeFiles.Add(sourceFile); + } + } + } + + if (wixipl == null && codeFiles.Count == 0) + { + this.Messaging.Write(ErrorMessages.NoSourceFiles()); + } + else if (wixipl != null && codeFiles.Count != 0) + { + this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive()); + } + } + + private IReadOnlyList CompilePhase(IDictionary preprocessorVariables, IEnumerable sourceFiles, CancellationToken cancellationToken) + { + var intermediates = new List(); + + foreach (var sourceFile in sourceFiles) + { + var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath, cancellationToken); + + if (this.Messaging.EncounteredError) + { + continue; + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.Platform = this.Platform; + context.Source = document; + context.CancellationToken = cancellationToken; + + Intermediate intermediate = null; + try + { + var compiler = this.ServiceProvider.GetService(); + intermediate = compiler.Compile(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + continue; + } + + intermediates.Add(intermediate); + } + + return intermediates; + } + + private Intermediate LibraryPhase(IReadOnlyCollection intermediates, IReadOnlyCollection localizations, bool bindFiles, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) + { + var context = this.ServiceProvider.GetService(); + context.BindFiles = bindFiles; + context.BindPaths = bindPaths; + context.Extensions = this.ExtensionManager.GetServices(); + context.Localizations = localizations; + context.Intermediates = intermediates; + context.CancellationToken = cancellationToken; + + Intermediate library = null; + try + { + var librarian = this.ServiceProvider.GetService(); + library = librarian.Combine(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + return library; + } + + private Intermediate LinkPhase(IEnumerable intermediates, IEnumerable libraryFiles, ISymbolDefinitionCreator creator, CancellationToken cancellationToken) + { + var libraries = this.LoadLibraries(libraryFiles, creator); + + if (this.Messaging.EncounteredError) + { + return null; + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.ExtensionData = this.ExtensionManager.GetServices(); + context.ExpectedOutputType = this.OutputType; + context.Intermediates = intermediates.Concat(libraries).ToList(); + context.SymbolDefinitionCreator = creator; + context.CancellationToken = cancellationToken; + + var linker = this.ServiceProvider.GetService(); + return linker.Link(context); + } + + private void BindPhase(Intermediate output, IReadOnlyCollection localizations, IReadOnlyCollection filterCultures, string cabCachePath, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) + { + var intermediateFolder = this.IntermediateFolder; + if (String.IsNullOrEmpty(intermediateFolder)) + { + intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + } + + IResolveResult resolveResult; + { + var context = this.ServiceProvider.GetService(); + context.BindPaths = bindPaths; + context.Extensions = this.ExtensionManager.GetServices(); + context.ExtensionData = this.ExtensionManager.GetServices(); + context.FilterCultures = filterCultures; + context.IntermediateFolder = intermediateFolder; + context.IntermediateRepresentation = output; + context.Localizations = localizations; + context.CancellationToken = cancellationToken; + + var resolver = this.ServiceProvider.GetService(); + resolveResult = resolver.Resolve(context); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + IBindResult bindResult = null; + try + { + { + var context = this.ServiceProvider.GetService(); + //context.CabbingThreadCount = this.CabbingThreadCount; + context.CabCachePath = cabCachePath; + context.ResolvedCodepage = resolveResult.Codepage; + context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage; + context.ResolvedLcid = resolveResult.PackageLcid; + context.DefaultCompressionLevel = this.DefaultCompressionLevel; + context.DelayedFields = resolveResult.DelayedFields; + context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; + context.Extensions = this.ExtensionManager.GetServices(); + context.FileSystemExtensions = this.ExtensionManager.GetServices(); + context.Ices = this.commandLine.Ices; + context.IntermediateFolder = intermediateFolder; + context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; + context.OutputPath = this.OutputFile; + context.PdbType = this.PdbType; + context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); + context.SuppressIces = this.commandLine.SuppressIces; + context.SuppressValidation = this.commandLine.SuppressValidation; + context.CancellationToken = cancellationToken; + + var binder = this.ServiceProvider.GetService(); + bindResult = binder.Bind(context); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + { + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.TrackedFiles = bindResult.TrackedFiles; + context.FileTransfers = bindResult.FileTransfers; + context.IntermediateFolder = intermediateFolder; + context.ContentsFile = this.ContentsFile; + context.OutputsFile = this.OutputsFile; + context.BuiltOutputsFile = this.BuiltOutputsFile; + context.ResetAcls = this.commandLine.ResetAcls; + context.CancellationToken = cancellationToken; + + var layout = this.ServiceProvider.GetService(); + layout.Layout(context); + } + } + finally + { + bindResult?.Dispose(); + } + } + + private IEnumerable LoadLibraries(IEnumerable libraryFiles, ISymbolDefinitionCreator creator) + { + try + { + return Intermediate.Load(libraryFiles, creator); + } + catch (WixCorruptFileException e) + { + this.Messaging.Write(e.Error); + } + catch (WixUnexpectedFileFormatException e) + { + this.Messaging.Write(e.Error); + } + + return Array.Empty(); + } + + private IReadOnlyList LoadLocalizationFiles(IEnumerable locFiles, IDictionary preprocessorVariables, CancellationToken cancellationToken) + { + var localizations = new List(); + var parser = this.ServiceProvider.GetService(); + + foreach (var loc in locFiles) + { + var document = this.Preprocess(preprocessorVariables, loc, cancellationToken); + + if (this.Messaging.EncounteredError) + { + continue; + } + + var localization = parser.ParseLocalization(document); + localizations.Add(localization); + } + + return localizations; + } + + private XDocument Preprocess(IDictionary preprocessorVariables, string sourcePath, CancellationToken cancellationToken) + { + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.Platform = this.Platform; + context.IncludeSearchPaths = this.IncludeSearchPaths; + context.SourcePath = sourcePath; + context.Variables = preprocessorVariables; + context.CancellationToken = cancellationToken; + + IPreprocessResult result = null; + try + { + var preprocessor = this.ServiceProvider.GetService(); + result = preprocessor.Preprocess(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + return result?.Document; + } + + private static string DefaultExtensionForSectionType(SectionType sectionType) + { + switch (sectionType) + { + case SectionType.Bundle: + return ".exe"; + case SectionType.Module: + return ".msm"; + case SectionType.Product: + return ".msi"; + case SectionType.PatchCreation: + return ".pcp"; + case SectionType.Patch: + return ".msp"; + case SectionType.Fragment: + case SectionType.Unknown: + default: + return ".wix"; + } + } + + private static string DefaultExtensionForOutputType(OutputType outputType) + { + switch (outputType) + { + case OutputType.Bundle: + return ".exe"; + case OutputType.Library: + return ".wixlib"; + case OutputType.Module: + return ".msm"; + case OutputType.Patch: + return ".msp"; + case OutputType.PatchCreation: + return ".pcp"; + case OutputType.Product: + return ".msi"; + case OutputType.Transform: + return ".mst"; + case OutputType.IntermediatePostLink: + return ".wixipl"; + case OutputType.Unknown: + default: + return ".wix"; + } + } + + private class CommandLine + { + private static readonly char[] BindPathSplit = { '=' }; + + public bool BindFiles { get; private set; } + + public List BindPaths { get; } = new List(); + + public string CabCachePath { get; private set; } + + public List Cultures { get; } = new List(); + + public List Defines { get; } = new List(); + + public List IncludeSearchPaths { get; } = new List(); + + public List LocalizationFilePaths { get; } = new List(); + + public List LibraryFilePaths { get; } = new List(); + + public List SourceFilePaths { get; } = new List(); + + public Platform Platform { get; private set; } + + public string PdbFile { get; private set; } + + public PdbType PdbType { get; private set; } + + public bool ShowLogo { get; private set; } + + public bool ShowHelp { get; private set; } + + public string IntermediateFolder { get; private set; } + + public string OutputFile { get; private set; } + + public string OutputType { get; private set; } + + public CompressionLevel? DefaultCompressionLevel { get; private set; } + + public string ContentsFile { get; private set; } + + public string OutputsFile { get; private set; } + + public string BuiltOutputsFile { get; private set; } + + public List Ices { get; } = new List(); + + public List SuppressIces { get; } = new List(); + + public bool SuppressValidation { get; set; } + + public bool ResetAcls { get; set; } + + public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) + { + this.ServiceProvider = serviceProvider; + this.Messaging = messaging; + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + public bool TryParseArgument(string arg, ICommandLineParser parser) + { + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1).ToLowerInvariant(); + switch (parameter) + { + case "?": + case "h": + case "help": + this.ShowHelp = true; + return true; + + case "arch": + case "platform": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out Platform platform)) + { + this.Platform = platform; + return true; + } + break; + } + + case "bf": + case "bindfiles": + this.BindFiles = true; + return true; + + case "bindpath": + { + var value = parser.GetNextArgumentOrError(arg); + if (value != null && this.TryParseBindPath(value, out var bindPath)) + { + this.BindPaths.Add(bindPath); + return true; + } + return false; + } + + case "cc": + this.CabCachePath = parser.GetNextArgumentOrError(arg); + return true; + + case "culture": + parser.GetNextArgumentOrError(arg, this.Cultures); + return true; + + case "contentsfile": + this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "outputsfile": + this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "builtoutputsfile": + this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "d": + case "define": + parser.GetNextArgumentOrError(arg, this.Defines); + return true; + + case "dcl": + case "defaultcompressionlevel": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out CompressionLevel compressionLevel)) + { + this.DefaultCompressionLevel = compressionLevel; + return true; + } + return false; + } + + case "i": + case "includepath": + parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); + return true; + + case "ice": + { + var value = parser.GetNextArgumentOrError(arg); + this.Ices.Add(value); + return true; + } + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); + return true; + + case "loc": + parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths); + return true; + + case "lib": + parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths); + return true; + + case "o": + case "out": + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "outputtype": + this.OutputType = parser.GetNextArgumentOrError(arg); + return true; + + case "pdb": + this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "pdbtype": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out PdbType pdbType)) + { + this.PdbType = pdbType; + return true; + } + return false; + } + + case "sice": + { + var value = parser.GetNextArgumentOrError(arg); + this.SuppressIces.Add(value); + return true; + } + + case "nologo": + this.ShowLogo = false; + return true; + + case "v": + case "verbose": + this.Messaging.ShowVerboseMessages = true; + return true; + + case "sval": + this.SuppressValidation = true; + return true; + + case "resetacls": + this.ResetAcls = true; + return true; + } + + if (parameter.StartsWith("sw")) + { + this.ParseSuppressWarning(parameter, "sw".Length, parser); + return true; + } + else if (parameter.StartsWith("suppresswarning")) + { + this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); + return true; + } + else if (parameter.StartsWith("wx")) + { + this.ParseWarningAsError(parameter, "wx".Length, parser); + return true; + } + + return false; + } + else + { + parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths); + return true; + } + } + + public string CalculateIntermedateFolder() + { + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; + } + + public OutputType CalculateOutputType() + { + if (String.IsNullOrEmpty(this.OutputType)) + { + this.OutputType = Path.GetExtension(this.OutputFile); + } + + switch (this.OutputType?.ToLowerInvariant()) + { + case "bundle": + case ".exe": + return Data.OutputType.Bundle; + + case "library": + case ".wixlib": + return Data.OutputType.Library; + + case "module": + case ".msm": + return Data.OutputType.Module; + + case "patch": + case ".msp": + return Data.OutputType.Patch; + + case ".pcp": + return Data.OutputType.PatchCreation; + + case "product": + case "package": + case ".msi": + return Data.OutputType.Product; + + case "transform": + case ".mst": + return Data.OutputType.Transform; + + case "intermediatepostlink": + case ".wixipl": + return Data.OutputType.IntermediatePostLink; + } + + return Data.OutputType.Unknown; + } + + public IReadOnlyList CalculateFilterCultures() + { + var result = new List(); + + if (this.Cultures == null) + { + } + else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) + { + // When null is used treat it as if cultures wasn't specified. This is + // needed for batching in the MSBuild task since MSBuild doesn't support + // empty items. + } + else + { + foreach (var culture in this.Cultures) + { + // Neutral is different from null. For neutral we still want to do culture filtering. + // Set the culture to the empty string = identifier for the invariant culture. + var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; + result.Add(filter); + } + } + + return result; + } + + public IDictionary GatherPreprocessorVariables() + { + var variables = new Dictionary(); + + foreach (var pair in this.Defines) + { + var value = pair.Split(new[] { '=' }, 2); + + if (variables.ContainsKey(value[0])) + { + this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); + continue; + } + + variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); + } + + return variables; + } + + public IEnumerable GatherSourceFiles(string intermediateDirectory) + { + var files = new List(); + + foreach (var item in this.SourceFilePaths) + { + var sourcePath = item; + var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); + + files.Add(new SourceFile(sourcePath, outputPath)); + } + + return files; + } + + private bool TryParseBindPath(string bindPath, out IBindPath bp) + { + var namedPath = bindPath.Split(BindPathSplit, 2); + + bp = this.ServiceProvider.GetService(); + + if (1 == namedPath.Length) + { + bp.Path = namedPath[0]; + } + else + { + bp.Name = namedPath[0]; + bp.Path = namedPath[1]; + } + + if (File.Exists(bp.Path)) + { + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); + return false; + } + + return true; + } + + private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.SuppressAllWarnings = true; + } + else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) + { + this.Messaging.SuppressWarningMessage(suppressWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); + } + } + + private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.WarningsAsError = true; + } + else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) + { + this.Messaging.ElevateWarningMessage(elevateWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLine.cs b/src/wix/WixToolset.Core/CommandLine/CommandLine.cs new file mode 100644 index 00000000..b87b6a5d --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLine.cs @@ -0,0 +1,199 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal enum CommandTypes + { + Unknown, + Build, + Preprocess, + Compile, + Link, + Bind, + Decompile, + } + + internal class CommandLine : ICommandLine + { + public CommandLine(IServiceProvider serviceProvider) => this.ServiceProvider = serviceProvider; + + private IServiceProvider ServiceProvider { get; } + + public ICommandLineCommand CreateCommand(string[] args) + { + var arguments = this.ServiceProvider.GetService(); + arguments.Populate(args); + + this.LoadExtensions(arguments.Extensions); + + return this.ParseStandardCommandLine(arguments); + } + + public ICommandLineCommand CreateCommand(string commandLine) + { + var arguments = this.ServiceProvider.GetService(); + arguments.Populate(commandLine); + + this.LoadExtensions(arguments.Extensions); + + return this.ParseStandardCommandLine(arguments); + } + + public ICommandLineCommand ParseStandardCommandLine(ICommandLineArguments arguments) + { + var context = this.ServiceProvider.GetService(); + context.ExtensionManager = this.ServiceProvider.GetService(); + context.Arguments = arguments; + + var command = this.Parse(context); + + if (command.ShowLogo) + { + var branding = this.ServiceProvider.GetService(); + Console.WriteLine(branding.ReplacePlaceholders("[AssemblyProduct] [AssemblyDescription] version [FileVersion]")); + Console.WriteLine(branding.ReplacePlaceholders("[AssemblyCopyright]")); + } + + return command; + } + + private void LoadExtensions(string[] extensions) + { + var extensionManager = this.ServiceProvider.GetService(); + + foreach (var extension in extensions) + { + extensionManager.Load(extension); + } + } + + private ICommandLineCommand Parse(ICommandLineContext context) + { + var branding = context.ServiceProvider.GetService(); + var extensions = context.ExtensionManager.GetServices(); + + foreach (var extension in extensions) + { + extension.PreParse(context); + } + + ICommandLineCommand command = null; + var parser = context.Arguments.Parse(); + + while (command?.StopParsing != true && + String.IsNullOrEmpty(parser.ErrorArgument) && + parser.TryGetNextSwitchOrArgument(out var arg)) + { + if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. + { + continue; + } + + // First argument must be the command or global switch (that creates a command). + if (command == null) + { + if (!this.TryParseCommand(arg, parser, extensions, out command)) + { + parser.ReportErrorArgument(arg); + } + } + else if (parser.IsSwitch(arg)) + { + if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) + { + parser.ReportErrorArgument(arg); + } + } + else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && !command.TryParseArgument(parser, arg)) + { + parser.ReportErrorArgument(arg); + } + } + + foreach (var extension in extensions) + { + extension.PostParse(); + } + + return command ?? new HelpCommand(extensions, branding); + } + + private bool TryParseCommand(string arg, ICommandLineParser parser, IEnumerable extensions, out ICommandLineCommand command) + { + command = null; + + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + case "-help": + var branding = this.ServiceProvider.GetService(); + command = new HelpCommand(extensions, branding); + break; + + case "version": + case "-version": + command = new VersionCommand(); + break; + } + } + else + { + if (Enum.TryParse(arg, true, out CommandTypes commandType)) + { + switch (commandType) + { + case CommandTypes.Build: + command = new BuildCommand(this.ServiceProvider); + break; + + case CommandTypes.Compile: + command = new CompileCommand(this.ServiceProvider); + break; + + case CommandTypes.Decompile: + command = new DecompileCommand(this.ServiceProvider); + break; + } + } + else + { + foreach (var extension in extensions) + { + if (extension.TryParseCommand(parser, arg, out command)) + { + break; + } + + command = null; + } + } + } + + return command != null; + } + + private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable extensions) + { + foreach (var extension in extensions) + { + if (extension.TryParseArgument(parse, arg)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs new file mode 100644 index 00000000..40b8b320 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs @@ -0,0 +1,207 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CommandLineArguments : ICommandLineArguments + { + public CommandLineArguments(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + public string[] OriginalArguments { get; set; } + + public string[] Arguments { get; set; } + + public string[] Extensions { get; set; } + + public string ErrorArgument { get; set; } + + private IMessaging Messaging { get; } + + public void Populate(string commandLine) + { + var args = CommandLineArguments.ParseArgumentsToArray(commandLine); + + this.Populate(args.ToArray()); + } + + public void Populate(string[] args) + { + this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args); + + this.ProcessArgumentsAndParseExtensions(this.OriginalArguments); + } + + public ICommandLineParser Parse() => new CommandLineParser(this.Messaging, this.Arguments, this.ErrorArgument); + + private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) + { + var args = new List(); + + foreach (var arg in commandLineArguments) + { + if (arg != null) + { + if ('@' == arg[0]) + { + var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1)); + args.AddRange(responseFileArguments); + } + else + { + args.Add(arg); + } + } + } + + this.OriginalArguments = args.ToArray(); + } + + private void ProcessArgumentsAndParseExtensions(string[] args) + { + var arguments = new List(); + var extensions = new List(); + + for (var i = 0; i < args.Length; ++i) + { + var arg = args[i]; + + if ("-ext" == arg || "/ext" == arg) + { + if (!CommandLineArguments.IsSwitchAt(args, ++i)) + { + extensions.Add(args[i]); + } + else + { + this.ErrorArgument = arg; + break; + } + } + else + { + arguments.Add(arg); + } + } + + this.Arguments = arguments.ToArray(); + this.Extensions = extensions.ToArray(); + } + + private static List ParseResponseFile(string responseFile) + { + string arguments; + + using (var reader = new StreamReader(responseFile)) + { + arguments = reader.ReadToEnd(); + } + + return CommandLineArguments.ParseArgumentsToArray(arguments); + } + + private static List ParseArgumentsToArray(string arguments) + { + // Scan and parse the arguments string, dividing up the arguments based on whitespace. + // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed. + // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments. + // Escaped quotes and escaped backslashes also need to be unescaped by this process. + + // Collects the final list of arguments to be returned. + var argsList = new List(); + + // True if we are inside an unescaped quote, meaning whitespace should be ignored. + var insideQuote = false; + + // Index of the start of the current argument substring; either the start of the argument + // or the start of a quoted or unquoted sequence within it. + var partStart = 0; + + // The current argument string being built; when completed it will be added to the list. + var arg = new StringBuilder(); + + for (var i = 0; i <= arguments.Length; i++) + { + if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote)) + { + // Reached a whitespace separator or the end of the string. + + // Finish building the current argument. + arg.Append(arguments.Substring(partStart, i - partStart)); + + // Skip over the whitespace character. + partStart = i + 1; + + // Add the argument to the list if it's not empty. + if (arg.Length > 0) + { + argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString())); + arg.Length = 0; + } + } + else if (i > partStart && arguments[i - 1] == '\\') + { + // Check the character following an unprocessed backslash. + // Unescape quotes, and backslashes followed by a quote. + if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"')) + { + // Unescape the quote or backslash by skipping the preceeding backslash. + arg.Append(arguments.Substring(partStart, i - 1 - partStart)); + arg.Append(arguments[i]); + partStart = i + 1; + } + } + else if (arguments[i] == '"') + { + // Add the quoted or unquoted section to the argument string. + arg.Append(arguments.Substring(partStart, i - partStart)); + + // And skip over the quote character. + partStart = i + 1; + + insideQuote = !insideQuote; + } + } + + return argsList; + } + + private static string ExpandEnvironmentVariables(string arguments) + { + var id = Environment.GetEnvironmentVariables(); + + var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)"); + var matches = regex.Matches(arguments); + + var value = String.Empty; + for (var i = 0; i <= (matches.Count - 1); i++) + { + try + { + var key = matches[i].Value; + regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)")); + value = id[key].ToString(); + arguments = regex.Replace(arguments, value); + } + catch (NullReferenceException) + { + // Collapse unresolved environment variables. + arguments = regex.Replace(arguments, value); + } + } + + return arguments; + } + + private static bool IsSwitchAt(string[] args, int index) => args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]); + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs new file mode 100644 index 00000000..8d5cf120 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs @@ -0,0 +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.Core.CommandLine +{ + using System; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CommandLineContext : ICommandLineContext + { + public CommandLineContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IExtensionManager ExtensionManager { get; set; } + + public ICommandLineArguments Arguments { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs new file mode 100644 index 00000000..015d3e62 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs @@ -0,0 +1,270 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class CommandLineParser : ICommandLineParser + { + private const string ExpectedArgument = "expected argument"; + + public string ErrorArgument { get; private set; } + + private Queue RemainingArguments { get; } + + private IMessaging Messaging { get; } + + public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument) + { + this.Messaging = messaging; + this.RemainingArguments = new Queue(arguments); + this.ErrorArgument = errorArgument; + } + + public bool IsSwitch(string arg) + { + return !String.IsNullOrEmpty(arg) && '-' == arg[0]; + } + + public string GetArgumentAsFilePathOrError(string argument, string fileType) + { + if (!File.Exists(argument)) + { + this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType)); + return null; + } + + return argument; + } + + public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) + { + foreach (var path in this.GetFiles(argument, fileType)) + { + paths.Add(path); + } + } + + public string GetNextArgumentOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) + { + return argument; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentOrError(string commandLineSwitch, IList args) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + args.Add(arg); + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) + { + return directory; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) + { + directories.Add(directory); + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) + { + return path; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + foreach (var path in this.GetFiles(arg, fileType)) + { + paths.Add(path); + } + + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public void ReportErrorArgument(string argument, Message message = null) + { + this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument)); + this.ErrorArgument = argument; + } + + public bool TryGetNextSwitchOrArgument(out string arg) + { + if (this.RemainingArguments.Count > 0) + { + arg = this.RemainingArguments.Dequeue(); + return true; + } + + arg = null; + return false; + } + + private bool TryGetNextNonSwitchArgumentOrError(out string arg) + { + var result = this.TryGetNextSwitchOrArgument(out arg); + + if (!result || this.IsSwitch(arg)) + { + this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; + return false; + } + + return result; + } + + private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory) + { + directory = null; + + if (File.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); + return false; + } + + directory = this.VerifyPath(arg); + return directory != null; + } + + private bool TryGetFile(string commandlineSwitch, string arg, out string path) + { + path = null; + + if (String.IsNullOrEmpty(arg) || '-' == arg[0]) + { + this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); + } + else if (Directory.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); + } + else + { + path = this.VerifyPath(arg); + } + + return path != null; + } + + /// + /// Get a set of files that possibly have a search pattern in the path (such as '*'). + /// + /// Search path to find files in. + /// Type of file; typically "Source". + /// An array of files matching the search path. + /// + /// This method is written in this verbose way because it needs to support ".." in the path. + /// It needs the directory path isolated from the file name in order to use Directory.GetFiles + /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since + /// Path.GetDirectoryName does not support ".." in the path. + /// + private string[] GetFiles(string searchPath, string fileType) + { + if (null == searchPath) + { + throw new ArgumentNullException(nameof(searchPath)); + } + + // Convert alternate directory separators to the standard one. + var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); + var files = new string[0]; + + try + { + if (0 > lastSeparator) + { + files = Directory.GetFiles(".", filePath); + } + else // found directory separator + { + files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); + } + } + catch (DirectoryNotFoundException) + { + // Don't let this function throw the DirectoryNotFoundException. This exception + // occurs for non-existant directories and invalid characters in the searchPattern. + } + catch (ArgumentException) + { + // Don't let this function throw the ArgumentException. This exception + // occurs in certain situations such as when passing a malformed UNC path. + } + catch (IOException) + { + } + + if (0 == files.Length) + { + this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); + } + + return files; + } + + private string VerifyPath(string path) + { + string fullPath; + + if (0 <= path.IndexOf('\"')) + { + this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); + return null; + } + + try + { + fullPath = Path.GetFullPath(path); + } + catch (Exception e) + { + this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); + return null; + } + + return fullPath; + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs b/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs new file mode 100644 index 00000000..6e31b241 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs @@ -0,0 +1,94 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CompileCommand : ICommandLineCommand + { + public CompileCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + } + + public CompileCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, Platform platform) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + this.SourceFiles = sources; + this.PreprocessorVariables = preprocessorVariables; + this.Platform = platform; + } + + private IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; } + + public IExtensionManager ExtensionManager { get; } + + private IEnumerable SourceFiles { get; } + + private IDictionary PreprocessorVariables { get; } + + private Platform Platform { get; } + + public IReadOnlyCollection IncludeSearchPaths { get; } + + public bool ShowLogo => throw new NotImplementedException(); + + public bool StopParsing => throw new NotImplementedException(); + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => throw new NotImplementedException(); + + public Task ExecuteAsync(CancellationToken _) + { + foreach (var sourceFile in this.SourceFiles) + { + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.Platform = this.Platform; + context.IncludeSearchPaths = this.IncludeSearchPaths; + context.SourcePath = sourceFile.SourcePath; + context.Variables = this.PreprocessorVariables; + + IPreprocessResult result = null; + try + { + var preprocessor = this.ServiceProvider.GetService(); + result = preprocessor.Preprocess(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + continue; + } + + var compileContext = this.ServiceProvider.GetService(); + compileContext.Extensions = this.ExtensionManager.GetServices(); + compileContext.Platform = this.Platform; + compileContext.Source = result?.Document; + + var compiler = this.ServiceProvider.GetService(); + var intermediate = compiler.Compile(compileContext); + + intermediate.Save(sourceFile.OutputPath); + } + + return Task.FromResult(0); + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs new file mode 100644 index 00000000..fc0ab0c9 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs @@ -0,0 +1,256 @@ +// 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.CommandLine +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileCommand : ICommandLineCommand + { + private readonly CommandLine commandLine; + + public DecompileCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.commandLine = new CommandLine(this.Messaging); + } + + public bool ShowLogo => this.commandLine.ShowLogo; + + public bool StopParsing => this.commandLine.ShowHelp; + + private IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; } + + public Task ExecuteAsync(CancellationToken _) + { + if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath)) + { + Console.WriteLine("TODO: Show decompile command help"); + return Task.FromResult(-1); + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ServiceProvider.GetService().GetServices(); + context.DecompilePath = this.commandLine.DecompileFilePath; + context.DecompileType = this.commandLine.CalculateDecompileType(); + context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); + context.OutputPath = this.commandLine.CalculateOutputPath(); + + try + { + var decompiler = this.ServiceProvider.GetService(); + var result = decompiler.Decompile(context); + + if (!this.Messaging.EncounteredError) + { + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath))); + result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + return Task.FromResult(1); + } + + return Task.FromResult(0); + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + return this.commandLine.TryParseArgument(argument, parser); + } + + private class CommandLine + { + public CommandLine(IMessaging messaging) + { + this.Messaging = messaging; + } + + private IMessaging Messaging { get; } + + public string DecompileFilePath { get; private set; } + + public string DecompileType { get; private set; } + + public Platform Platform { get; private set; } + + public bool ShowLogo { get; private set; } + + public bool ShowHelp { get; private set; } + + public string IntermediateFolder { get; private set; } + + public string OutputFile { get; private set; } + + public bool TryParseArgument(string arg, ICommandLineParser parser) + { + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + this.ShowHelp = true; + return true; + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); + return true; + + case "o": + case "out": + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "nologo": + this.ShowLogo = false; + return true; + + case "v": + case "verbose": + this.Messaging.ShowVerboseMessages = true; + return true; + } + + if (parameter.StartsWith("sw")) + { + this.ParseSuppressWarning(parameter, "sw".Length, parser); + return true; + } + else if (parameter.StartsWith("suppresswarning")) + { + this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); + return true; + } + else if (parameter.StartsWith("wx")) + { + this.ParseWarningAsError(parameter, "wx".Length, parser); + return true; + } + } + else + { + if (String.IsNullOrEmpty(this.DecompileFilePath)) + { + this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file"); + return true; + } + else if (String.IsNullOrEmpty(this.OutputFile)) + { + this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file"); + return true; + } + } + + return false; + } + + public OutputType CalculateDecompileType() + { + if (String.IsNullOrEmpty(this.DecompileType)) + { + this.DecompileType = Path.GetExtension(this.DecompileFilePath); + } + + switch (this.DecompileType.ToLowerInvariant()) + { + case "bundle": + case ".exe": + return OutputType.Bundle; + + case "library": + case ".wixlib": + return OutputType.Library; + + case "module": + case ".msm": + return OutputType.Module; + + case "patch": + case ".msp": + return OutputType.Patch; + + case ".pcp": + return OutputType.PatchCreation; + + case "product": + case "package": + case ".msi": + return OutputType.Product; + + case "transform": + case ".mst": + return OutputType.Transform; + + case "intermediatepostlink": + case ".wixipl": + return OutputType.IntermediatePostLink; + } + + return OutputType.Unknown; + } + + public string CalculateIntermedateFolder() + { + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; + } + + public string CalculateOutputPath() + { + return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile; + } + + private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.SuppressAllWarnings = true; + } + else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) + { + this.Messaging.SuppressWarningMessage(suppressWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); + } + } + + private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.WarningsAsError = true; + } + else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) + { + this.Messaging.ElevateWarningMessage(elevateWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs b/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs new file mode 100644 index 00000000..6a5ac183 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs @@ -0,0 +1,66 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class HelpCommand : ICommandLineCommand + { + private static readonly ExtensionCommandLineSwitch[] BuiltInSwitches = new ExtensionCommandLineSwitch[] + { + new ExtensionCommandLineSwitch { Switch = "build", Description = "Build a wixlib, package or bundle." }, + new ExtensionCommandLineSwitch { Switch = "decompile", Description = "Decompile a package or bundle into source code." }, + }; + + public HelpCommand(IEnumerable extensions, IWixBranding branding) + { + this.Extensions = extensions; + this.Branding = branding; + } + + public bool ShowLogo => true; + + public bool StopParsing => true; + + private IEnumerable Extensions { get; } + + private IWixBranding Branding { get; } + + public Task ExecuteAsync(CancellationToken _) + { + var commandLineSwitches = new List(BuiltInSwitches); + commandLineSwitches.AddRange(this.Extensions.SelectMany(e => e.CommandLineSwitches).OrderBy(s => s.Switch, StringComparer.Ordinal)); + + Console.WriteLine(); + Console.WriteLine("Usage: wix [option]"); + Console.WriteLine("Usage: wix [command]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -h|--help Show command line help."); + Console.WriteLine(" --version Display WiX Toolset version in use."); + Console.WriteLine(); + + Console.WriteLine("Commands:"); + foreach (var commandLineSwitch in commandLineSwitches) + { + Console.WriteLine(" {0,-17} {1}", commandLineSwitch.Switch, commandLineSwitch.Description); + } + + Console.WriteLine(); + Console.WriteLine("Run 'wix [command] --help' for more information on a command."); + Console.WriteLine(); + Console.WriteLine(this.Branding.ReplacePlaceholders("For more information see: [SupportUrl]")); + + return Task.FromResult(-1); + } + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs b/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs new file mode 100644 index 00000000..01a7d0e6 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs @@ -0,0 +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.Core.CommandLine +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class VersionCommand : ICommandLineCommand + { + public bool ShowLogo => true; + + public bool StopParsing => true; + + public Task ExecuteAsync(CancellationToken cancellationToken) + { + Console.WriteLine(ThisAssembly.AssemblyInformationalVersion); + + return Task.FromResult(0); + } + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments + } +} diff --git a/src/wix/WixToolset.Core/Common.cs b/src/wix/WixToolset.Core/Common.cs new file mode 100644 index 00000000..848f009a --- /dev/null +++ b/src/wix/WixToolset.Core/Common.cs @@ -0,0 +1,832 @@ +// 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; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Common Wix utility methods and types. + /// + internal static class Common + { + private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; + private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; + + internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * " + internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' + internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' + + public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler) + { + const string root = @"C:\"; + if (!Path.IsPathRooted(relativePath)) + { + var normalizedPath = Path.GetFullPath(root + relativePath); + if (normalizedPath.StartsWith(root)) + { + var canonicalizedPath = normalizedPath.Substring(root.Length); + if (canonicalizedPath != relativePath) + { + messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath)); + } + return canonicalizedPath; + } + } + + messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath)); + return relativePath; + } + + /// + /// Gets a valid code page from the given web name or integer value. + /// + /// A code page web name or integer value as a string. + /// Whether to allow -1 which does not change the database code pages. This may be the case with wxl files. + /// Whether to allow Unicode (UCS) or UTF code pages. + /// Source line information for the current authoring. + /// A valid code page number. + /// The value is an integer less than 0 or greater than 65535. + /// is null. + /// The value doesn't not represent a valid code page name or integer value. + /// The code page is invalid for summary information. + public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + try + { + Encoding encoding; + + // Check if a integer as a string was passed. + if (Int32.TryParse(value, out var codePage)) + { + if (0 == codePage) + { + // 0 represents a neutral database + return 0; + } + else if (allowNoChange && -1 == codePage) + { + // -1 means no change to the database code page + return -1; + } + + encoding = Encoding.GetEncoding(codePage); + } + else + { + encoding = Encoding.GetEncoding(value); + } + + // Windows Installer parses some code page references + // as unsigned shorts which fail to open the database. + if (onlyAnsi) + { + codePage = encoding.CodePage; + if (0 > codePage || Int16.MaxValue < codePage) + { + throw new WixException(ErrorMessages.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage)); + } + } + + if (encoding == null) + { + throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage)); + } + + return encoding.CodePage; + } + catch (ArgumentException ex) + { + // Rethrow as NotSupportedException since either can be thrown + // if the system does not support the specified code page. + throw new NotSupportedException(ex.Message, ex); + } + } + + /// + /// Verifies if an identifier is a valid binder variable name. + /// + /// Binder variable name to verify. + /// True if the identifier is a valid binder variable name. + public static bool IsValidBinderVariable(string variable) + { + return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); + } + + /// + /// Verifies if a string contains a valid binder variable name. + /// + /// String to verify. + /// True if the string contains a valid binder variable name. + public static bool ContainsValidBinderVariable(string verify) + { + return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); + } + + /// + /// Verifies the given string is a valid 4-part version module or bundle version. + /// + /// The version to verify. + /// True if version is a valid module or bundle version. + public static bool IsValidFourPartVersion(string version) + { + if (!Common.IsValidBinderVariable(version)) + { + if (!Version.TryParse(version, out var ver) || 65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision) + { + return false; + } + } + + return true; + } + + public static bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + else if (filename.Length > 259) + { + return false; + } + + // Check for a non-period character (all periods is not legal) + var allPeriods = true; + foreach (var character in filename) + { + if ('.' != character) + { + allPeriods = false; + break; + } + } + + if (allPeriods) + { + return false; + } + + if (allowWildcards) + { + return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1; + } + else if (allowRelative) + { + return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1; + } + else + { + return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1; + } + } + + public static bool IsValidShortFilename(string filename, bool allowWildcards) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + + if (allowWildcards) + { + var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters); + if (expectedDot == -1) + { + } + else if (filename[expectedDot] != '.') + { + return false; + } + else if (expectedDot < filename.Length) + { + var extensionInvalids = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters, expectedDot + 1); + if (extensionInvalids != -1) + { + return false; + } + } + + var foundPeriod = false; + var beforePeriod = 0; + var afterPeriod = 0; + + // count the number of characters before and after the period + // '*' is not counted because it may represent zero characters + foreach (var character in filename) + { + if ('.' == character) + { + foundPeriod = true; + } + else if ('*' != character) + { + if (foundPeriod) + { + afterPeriod++; + } + else + { + beforePeriod++; + } + } + } + + if (8 >= beforePeriod && 3 >= afterPeriod) + { + return true; + } + + return false; + } + else + { + if (filename.Length > 12) + { + return false; + } + + var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters); + if (expectedDot == -1) + { + return filename.Length < 9; + } + else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length) + { + return false; + } + + var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); + return validExtension == -1; + } + } + + /// + /// Generate a new Windows Installer-friendly guid. + /// + /// A new guid. + public static string GenerateGuid() + { + return Guid.NewGuid().ToString("B").ToUpperInvariant(); + } + + /// + /// Generate an identifier by hashing data from the row. + /// + /// Three letter or less prefix for generated row identifier. + /// Information to hash. + /// The generated identifier. + public static string GenerateIdentifier(string prefix, params string[] args) + { + string base64; + + using (var sha1 = new SHA1CryptoServiceProvider()) + { + var combined = String.Join("|", args); + var data = Encoding.UTF8.GetBytes(combined); + var hash = sha1.ComputeHash(data); + base64 = Convert.ToBase64String(hash); + } + + var identifier = new StringBuilder(32); + identifier.Append(prefix); + identifier.Append(base64); + identifier.Length -= 1; // removes the trailing '=' from base64 + identifier.Replace('+', '.'); + identifier.Replace('/', '_'); + + return identifier.ToString(); + } + + /// + /// Return an identifier based on provided file or directory name + /// + /// File/directory name to generate identifer from + /// A version of the name that is a legal identifier. + internal static string GetIdentifierFromName(string name) + { + StringBuilder sb = null; + var offset = 0; + + // MSI identifiers must begin with an alphabetic character or an + // underscore. Prefix all other values with an underscore. + if (!ValidIdentifierChar(name[0], true)) + { + sb = new StringBuilder("_" + name); + offset = 1; + } + + for (var i = 0; i < name.Length; ++i) + { + if (!ValidIdentifierChar(name[i], false)) + { + if (sb == null) + { + sb = new StringBuilder(name); + } + + sb[i + offset] = '_'; + } + } + + return sb?.ToString() ?? name; + } + + /// + /// Checks if the string contains a property (i.e. "foo[Property]bar") + /// + /// String to evaluate for properties. + /// True if a property is found in the string. + internal static bool ContainsProperty(string possibleProperty) + { + var start = possibleProperty.IndexOf('['); + if (start != -1 && start < possibleProperty.Length - 2) + { + var end = possibleProperty.IndexOf(']', start + 1); + if (end > start + 1) + { + // Skip supported property modifiers. + if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!') + { + ++start; + } + + var id = possibleProperty.Substring(start + 1, end - 1); + + if (Common.IsIdentifier(id)) + { + return true; + } + } + } + + return false; + } + + /// + /// Recursively loops through a directory, changing an attribute on all of the underlying files. + /// An example is to add/remove the ReadOnly flag from each file. + /// + /// The directory path to start deleting from. + /// The FileAttribute to change on each file. + /// The message handler. + /// If true, add the attribute to each file. If false, remove it. + private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessaging messageHandler) + { + foreach (var subDirectory in Directory.GetDirectories(path)) + { + RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler); + } + + foreach (var filePath in Directory.GetFiles(path)) + { + var attributes = File.GetAttributes(filePath); + if (markAttribute) + { + attributes = attributes | fileAttribute; // add to list of attributes + } + else if (fileAttribute == (attributes & fileAttribute)) // if attribute set + { + attributes = attributes ^ fileAttribute; // remove from list of attributes + } + + try + { + File.SetAttributes(filePath, attributes); + } + catch (UnauthorizedAccessException) + { + messageHandler.Write(WarningMessages.AccessDeniedForSettingAttributes(null, filePath)); + } + } + } + + /// + /// 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. + /// Thus the returned array will always be of length 4. + /// + public static string[] GetNames(string value) + { + var targetSeparator = value.IndexOf(':'); + + // split source and target + string sourceName = null; + var 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) + { + var sourceLongNameSeparator = sourceName.IndexOf('|'); + if (0 <= sourceLongNameSeparator) + { + sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); + sourceName = sourceName.Substring(0, sourceLongNameSeparator); + } + } + + // split the target short and long names + var targetLongNameSeparator = targetName.IndexOf('|'); + 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 short 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) + { + targetName = null; + } + + if ("." == targetLongName) + { + targetLongName = null; + } + + // source name(s) + if ("." == sourceName) + { + sourceName = null; + } + + if ("." == sourceLongName) + { + sourceLongName = null; + } + + return new[] { targetName, targetLongName, sourceName, sourceLongName }; + } + + /// + /// 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) + { + var 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. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. + /// The attribute's value. + internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) + { + var value = attribute.Value; + + if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || + (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) + { + messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + return String.Empty; + } + + return value; + } + + /// + /// Verifies that a value is a legal identifier. + /// + /// The value to verify. + /// true if the value is an identifier; false otherwise. + public static bool IsIdentifier(string value) + { + if (String.IsNullOrEmpty(value)) + { + return false; + } + + for (var i = 0; i < value.Length; ++i) + { + if (!ValidIdentifierChar(value[i], i == 0)) + { + return false; + } + } + + return true; + } + + /// + /// Get an identifier attribute value and displays an error for an illegal identifier value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's identifier value or a special value if an error occurred. + internal static string GetAttributeIdentifierValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); + + if (Common.IsIdentifier(value)) + { + if (72 < value.Length) + { + messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return value; + } + else + { + if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal)) + { + messaging.Write(ErrorMessages.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else + { + messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return String.Empty; + } + } + + /// + /// Get an integer attribute value and displays an error for an illegal integer value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's integer value or a special value if an error occurred during conversion. + public static int GetAttributeIntegerValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); + + var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); + var integer = CompilerConstants.IllegalInteger; + + if (0 < value.Length) + { + if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer)) + { + if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) + { + messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); + } + else if (minimum > integer || maximum < integer) + { + messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); + integer = CompilerConstants.IllegalInteger; + } + } + else + { + messaging.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return integer; + } + + /// + /// Gets a yes/no value and displays an error for an illegal yes/no value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's YesNoType value. + internal static YesNoType GetAttributeYesNoValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); + var yesNo = YesNoType.IllegalValue; + + if ("yes".Equals(value) || "true".Equals(value)) + { + yesNo = YesNoType.Yes; + } + else if ("no".Equals(value) || "false".Equals(value)) + { + yesNo = YesNoType.No; + } + else + { + messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return yesNo; + } + + /// + /// Gets the text of an XElement. + /// + /// Element to get text. + /// The element's text. + internal static string GetInnerText(XElement node) + { + var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast().FirstOrDefault(); + return text?.Value; + } + + internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable) + { + parsedVariable = null; + + if (String.IsNullOrEmpty(value) || start >= value.Length) + { + return false; + } + + var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal); + if (startWixVariable == -1) + { + return false; + } + + var firstDot = value.IndexOf('.', startWixVariable + 1); + if (firstDot == -1) + { + return false; + } + + var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2); + if (ns != "loc" && ns != "bind" && ns != "wix") + { + return false; + } + + var closeParen = value.IndexOf(')', firstDot); + if (closeParen == -1) + { + return false; + } + + string name; + string scope = null; + string defaultValue = null; + + var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); + var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; + var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot); + + if (secondDot == -1) + { + name = value.Substring(firstDot + 1, end - firstDot - 1); + } + else + { + name = value.Substring(firstDot + 1, secondDot - firstDot - 1); + scope = value.Substring(secondDot + 1, end - secondDot - 1); + + if (!Common.IsIdentifier(scope)) + { + return false; + } + } + + if (!Common.IsIdentifier(name)) + { + return false; + } + + if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) + { + defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1); + } + + parsedVariable = new ParsedWixVariable + { + Index = startWixVariable, + Length = closeParen - startWixVariable + 1, + Namespace = ns, + Name = name, + Scope = scope, + DefaultValue = defaultValue + }; + + return true; + } + + /// + /// Display an unexpected attribute error. + /// + /// + /// Source line information about the owner element. + /// The attribute. + public static void UnexpectedAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + // Ignore elements defined by the W3C because we'll assume they are always right. + if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || + attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) + { + messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + } + } + + /// + /// Display an unsupported extension attribute error. + /// + /// + /// Source line information about the owner element. + /// The extension attribute. + internal static void UnsupportedExtensionAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute) + { + // Ignore elements defined by the W3C because we'll assume they are always right. + if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || + extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) + { + messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); + } + } + + private static bool ValidIdentifierChar(char c, bool firstChar) + { + return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c || + (!firstChar && (Char.IsDigit(c) || '.' == c)); + } + } +} diff --git a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs new file mode 100644 index 00000000..3f423034 --- /dev/null +++ b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs @@ -0,0 +1,291 @@ +// 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.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CompilerPayload + { + public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; + + public string Description { get; set; } + + public string DownloadUrl { get; set; } + + public string Hash { get; set; } + + public Identifier Id { get; set; } + + public bool IsRemoteAllowed { get; set; } + + public bool IsRequired { get; set; } = true; + + public string Name { get; set; } + + public string ProductName { get; set; } + + public long? Size { get; set; } + + public string SourceFile { get; set; } + + public string Version { get; set; } + + public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element) + { + this.Core = core; + this.Element = element; + this.SourceLineNumbers = sourceLineNumbers; + } + + private CompilerCore Core { get; } + + private XElement Element { get; } + + private SourceLineNumber SourceLineNumbers { get; } + + private void CalculateAndVerifyFields() + { + var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); + + if (String.IsNullOrEmpty(this.SourceFile)) + { + if (!String.IsNullOrEmpty(this.Name) && !isRemote) + { + this.SourceFile = Path.Combine("SourceDir", this.Name); + } + } + else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + if (String.IsNullOrEmpty(this.Name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile)); + } + else + { + this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name)); + } + } + + if (String.IsNullOrEmpty(this.SourceFile) && !isRemote) + { + if (this.IsRequired) + { + if (!this.IsRemoteAllowed) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); + } + } + } + else if (this.IsRemoteAllowed) + { + var isLocal = !String.IsNullOrEmpty(this.SourceFile); + + if (isLocal) + { + if (isRemote) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); + } + + if (!String.IsNullOrEmpty(this.Description)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Description", "SourceFile")); + } + + if (!String.IsNullOrEmpty(this.ProductName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "ProductName", "SourceFile")); + } + + if (this.Size.HasValue) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile")); + } + + if (!String.IsNullOrEmpty(this.Version)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Version", "SourceFile")); + } + } + else + { + if (String.IsNullOrEmpty(this.DownloadUrl)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); + } + + if (String.IsNullOrEmpty(this.Name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); + } + + if (!this.Size.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); + } + + if (YesNoDefaultType.Yes == this.Compressed) + { + this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); + } + + this.Compressed = YesNoDefaultType.No; + } + } + } + + public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null) + { + WixBundlePayloadSymbol symbol = null; + + if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName) + { + if (this.Compressed == YesNoDefaultType.No) + { + this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile)); + } + + if (!String.IsNullOrEmpty(this.DownloadUrl)) + { + this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id)); + } + + this.Compressed = YesNoDefaultType.Yes; + this.DownloadUrl = null; + } + + if (!this.Core.EncounteredError) + { + symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id) + { + Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name, + SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile }, + DownloadUrl = this.DownloadUrl, + Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, + UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. + DisplayName = this.ProductName, + Description = this.Description, + Hash = this.Hash, + FileSize = this.Size, + Version = this.Version, + }); + + this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId); + } + + return symbol; + } + + public void FinishCompilingPackage() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromFilename(); + + if (this.Id == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id")); + this.Id = Identifier.Invalid; + } + } + + public void FinishCompilingPackagePayload() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromFilename(); + this.GenerateIdFromPrefix("ppy"); + } + + public void FinishCompilingPayload() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromPrefix("pay"); + } + + private void GenerateIdFromFilename() + { + if (this.Id == null) + { + if (!String.IsNullOrEmpty(this.Name)) + { + this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name)); + } + else if (!String.IsNullOrEmpty(this.SourceFile)) + { + this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile)); + } + } + } + + private void GenerateIdFromPrefix(string prefix) + { + if (this.Id == null) + { + this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty); + } + } + + public void ParseCompressed(XAttribute attrib) + { + this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib); + } + + public void ParseDescription(XAttribute attrib) + { + this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseDownloadUrl(XAttribute attrib) + { + this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseHash(XAttribute attrib) + { + this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseId(XAttribute attrib) + { + this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib); + } + + public void ParseName(XAttribute attrib) + { + this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true); + if (!this.Core.IsValidLongFilename(this.Name, false, true)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name)); + } + } + + public void ParseProductName(XAttribute attrib) + { + this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseSize(XAttribute attrib) + { + this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue); + } + + public void ParseSourceFile(XAttribute attrib) + { + this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseVersion(XAttribute attrib) + { + this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + } +} diff --git a/src/wix/WixToolset.Core/CompileContext.cs b/src/wix/WixToolset.Core/CompileContext.cs new file mode 100644 index 00000000..d84d7aac --- /dev/null +++ b/src/wix/WixToolset.Core/CompileContext.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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class CompileContext : ICompileContext + { + internal CompileContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string CompilationId { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public Platform Platform { get; set; } + + public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64; + + public XDocument Source { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs new file mode 100644 index 00000000..c39bec70 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler.cs @@ -0,0 +1,8514 @@ +// 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.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB + private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + + private const char ComponentIdPlaceholderStart = (char)167; + private const char ComponentIdPlaceholderEnd = (char)167; + private Dictionary componentIdPlaceholders; + + // If these are true you know you are building a module or product + // but if they are false you cannot not be sure they will not end + // up a product or module. Use these flags carefully. + private bool compilingModule; + private bool compilingProduct; + + private string activeName; + private string activeLanguage; + + /// + /// Type of RadioButton element in a group. + /// + private enum RadioButtonType + { + /// Not set, yet. + NotSet, + + /// Text + Text, + + /// Bitmap + Bitmap, + + /// Icon + Icon, + } + + internal Compiler(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + public IMessaging Messaging { get; } + + private ICompileContext Context { get; set; } + + private CompilerCore Core { get; set; } + + /// + /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. + /// + /// The platform which the compiler will use when defaulting 64-bit attributes and elements. + public Platform CurrentPlatform => this.Context.Platform; + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages { get; set; } + + /// + /// Compiles the provided Xml document into an intermediate object + /// + /// Intermediate object representing compiled source document. + /// This method is not thread-safe. + public Intermediate Compile(ICompileContext context) + { + var target = new Intermediate(); + + if (String.IsNullOrEmpty(context.CompilationId)) + { + context.CompilationId = target.Id; + } + + this.Context = context; + + var extensionsByNamespace = new Dictionary(); + + foreach (var extension in this.Context.Extensions) + { + if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension)) + { + extensionsByNamespace.Add(extension.Namespace, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString())); + } + + extension.PreCompile(this.Context); + } + + // Try to compile it. + try + { + var parseHelper = this.Context.ServiceProvider.GetService(); + + this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); + this.Core.ShowPedanticMessages = this.ShowPedanticMessages; + this.componentIdPlaceholders = new Dictionary(); + + // parse the document + var source = this.Context.Source; + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root); + if ("Wix" == source.Root.Name.LocalName) + { + if (CompilerCore.WixNamespace == source.Root.Name.Namespace) + { + this.ParseWixElement(source.Root); + } + else // invalid or missing namespace + { + if (String.IsNullOrEmpty(source.Root.Name.NamespaceName)) + { + this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString())); + } + else + { + this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString())); + } + } + } + else + { + this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix")); + } + + // Resolve any Component Id placeholders compiled into the intermediate. + this.ResolveComponentIdPlaceholders(target); + } + finally + { + foreach (var extension in this.Context.Extensions) + { + extension.PostCompile(target); + } + + this.Core = null; + } + + target.UpdateLevel(Data.IntermediateLevels.Compiled); + + return this.Messaging.EncounteredError ? null : target; + } + + /// + /// Parses a Wix element. + /// + /// Element to parse. + private void ParseWixElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string requiredVersion = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "RequiredVersion": + requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != requiredVersion) + { + this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Bundle": + this.ParseBundleElement(child); + break; + case "Fragment": + this.ParseFragmentElement(child); + break; + case "Module": + this.ParseModuleElement(child); + break; + case "PatchCreation": + this.ParsePatchCreationElement(child); + break; + case "Package": + this.ParsePackageElement(child); + break; + case "Patch": + this.ParsePatchElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + private void ResolveComponentIdPlaceholders(Intermediate target) + { + if (0 < this.componentIdPlaceholders.Count) + { + foreach (var section in target.Sections) + { + foreach (var symbol in section.Symbols) + { + foreach (var field in symbol.Fields) + { + if (field != null && field.Type == IntermediateFieldType.String) + { + var data = field.AsString(); + if (!String.IsNullOrEmpty(data)) + { + var changed = false; + var start = data.IndexOf(ComponentIdPlaceholderStart); + while (start != -1) + { + var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1); + if (end == -1) + { + break; + } + + var placeholderId = data.Substring(start, end - start + 1); + if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value)) + { + var sb = new StringBuilder(data); + sb.Remove(start, end - start + 1); + sb.Insert(start, value); + + data = sb.ToString(); + changed = true; + + end = start + value.Length; + } + + start = data.IndexOf(ComponentIdPlaceholderStart, end); + } + + if (changed) + { + field.Overwrite(data); + } + } + } + } + } + } + } + } + + /// + /// Uppercases the first character of a string. + /// + /// String to uppercase first character of. + /// String with first character uppercased. + private static string UppercaseFirstChar(string s) + { + if (0 == s.Length) + { + return s; + } + + return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1)); + } + + /// + /// Lowercases the string if present. + /// + /// String to lowercase. + /// Null if the string is null, otherwise returns the lowercase. + private static string LowercaseOrNull(string s) + { + return s?.ToLowerInvariant(); + } + + /// + /// Adds a search property to the active section. + /// + /// Current source/line number of processing. + /// Property to add to search. + /// Signature for search. + private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature) + { + if (!this.Core.EncounteredError) + { + if (propertyId.Id != propertyId.Id.ToUpperInvariant()) + { + this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id)); + } + + this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature)) + { + PropertyRef = propertyId.Id, + SignatureRef = signature + }); + } + } + + /// + /// Adds a property to the active section. + /// + /// Current source/line number of processing. + /// Identifier of property to add. + /// Value of property. + /// Flag if property is an admin property. + /// Flag if property is a secure property. + /// Flag if property is to be hidden. + /// Adds the property to a new section. + private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment) + { + // properties without a valid identifier should not be processed any further + if (null == propertyId || String.IsNullOrEmpty(propertyId.Id)) + { + return; + } + + if (!String.IsNullOrEmpty(value)) + { + var start = value.IndexOf('['); + while (start != -1 && start < value.Length) + { + var end = value.IndexOf(']', start + 1); + if (end == -1) + { + break; + } + + var id = value.Substring(start + 1, end - 1); + if (Common.IsIdentifier(id)) + { + this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id)); + } + + start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1; + } + } + + if (!this.Core.EncounteredError) + { + var section = this.Core.ActiveSection; + + // Add the symbol to a separate section if requested. + if (fragment) + { + var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id); + + section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId); + + // Reference the property in the active section. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id); + } + + // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements + // the linker will remove these symbols before the final output is created. + section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId) + { + Value = value, + }); + + if (admin || hidden || secure) + { + this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section); + } + } + } + + private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null) + { + if (secure && property.Id != property.Id.ToUpperInvariant()) + { + this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id)); + } + + if (null == section) + { + section = this.Core.ActiveSection; + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table. + } + + section.AddSymbol(new WixPropertySymbol(sourceLineNumbers) + { + PropertyRef = property.Id, + Admin = admin, + Hidden = hidden, + Secure = secure + }); + } + + /// + /// Adds a "implemented category" registry key to active section. + /// + /// Current source/line number of processing. + /// GUID for category. + /// ClassId for to mark "implemented". + /// Identifier of parent component. + private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId); + } + + /// + /// Parses an application identifer element. + /// + /// Element to parse. + /// Identifier of parent component. + /// The required advertise state (set depending upon the parent). + /// Optional file identifier for CLSID when not advertised. + /// Optional TypeLib GUID for CLSID. + /// Optional TypeLib Version for CLSID Interfaces (if any). + private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string appId = null; + string remoteServerName = null; + string localService = null; + string serviceParameters = null; + string dllSurrogate = null; + bool? activateAtStorage = null; + var appIdAdvertise = YesNoType.NotSet; + bool? runAsInteractiveUser = null; + string description = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "ActivateAtStorage": + activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Advertise": + appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DllSurrogate": + dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "LocalService": + localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "RemoteServerName": + remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "RunAsInteractiveUser": + runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ServiceParameters": + serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == appId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise)) + { + this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString())); + } + else + { + advertise = appIdAdvertise; + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Class": + this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (YesNoType.Yes == advertise) + { + if (null != description) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId)) + { + AppId = appId, + RemoteServerName = remoteServerName, + LocalService = localService, + ServiceParameters = serviceParameters, + DllSurrogate = dllSurrogate, + ActivateAtStorage = activateAtStorage, + RunAsInteractiveUser = runAsInteractiveUser, + }); + } + } + else if (YesNoType.No == advertise) + { + if (null != description) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId); + } + else + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId); + } + + if (null != remoteServerName) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId); + } + + if (null != localService) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId); + } + + if (null != serviceParameters) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId); + } + + if (null != dllSurrogate) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId); + } + + if (true == activateAtStorage) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId); + } + + if (true == runAsInteractiveUser) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId); + } + } + } + + /// + /// Parses an AssemblyName element. + /// + /// File element to parse. + /// Parent's component id. + private void ParseAssemblyName(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id)) + { + ComponentRef = componentId, + Name = id, + Value = value, + }); + } + } + + /// + /// Parses a binary element. + /// + /// Element to parse. + /// Identifier for the new row. + private Identifier ParseBinaryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string sourceFile = null; + var suppressModularization = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!String.IsNullOrEmpty(id.Id)) // only check legal values + { + if (55 < id.Id.Length) + { + this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55)); + } + else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized + { + if (18 < id.Id.Length) + { + this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18)); + } + } + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) + { + Data = new IntermediateFieldPathValue { Path = sourceFile } + }); + + if (YesNoType.Yes == suppressModularization) + { + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); + } + } + + return id; + } + + /// + /// Parses an icon element. + /// + /// Element to parse. + /// Identifier for the new row. + private string ParseIconElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!String.IsNullOrEmpty(id.Id)) // only check legal values + { + if (57 < id.Id.Length) + { + this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57)); + } + else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized + { + if (20 < id.Id.Length) + { + this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20)); + } + } + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id) + { + Data = new IntermediateFieldPathValue { Path = sourceFile }, + }); + } + + return id.Id; + } + + /// + /// Parses an InstanceTransforms element. + /// + /// Element to parse. + private void ParseInstanceTransformsElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string property = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + // find unexpected child elements + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Instance": + this.ParseInstanceElement(child, property); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses an instance element. + /// + /// Element to parse. + /// Identifier of instance property. + private void ParseInstanceElement(XElement node, string propertyId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string productCode = null; + string productName = null; + string upgradeCode = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "ProductName": + productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == productCode) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id) + { + PropertyId = propertyId, + ProductCode = productCode, + ProductName = productName, + UpgradeCode = upgradeCode + }); + } + } + + /// + /// Parses a category element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseCategoryElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string appData = null; + string feature = null; + string qualifier = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "AppData": + appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + break; + case "Qualifier": + qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == qualifier) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers) + { + ComponentId = id, + Qualifier = qualifier, + ComponentRef = componentId, + AppData = appData, + FeatureRef = feature ?? Guid.Empty.ToString("B"), + }); + } + } + + /// + /// Parses a class element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional Advertise State for the parent AppId element (if any). + /// Optional file identifier for CLSID when not advertised. + /// Optional TypeLib GUID for CLSID. + /// Optional TypeLib Version for CLSID Interfaces (if any). + /// Optional parent AppId. + private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string appId = null; + string argument = null; + var class16bit = false; + var class32bit = false; + string classId = null; + var classAdvertise = YesNoType.NotSet; + var contexts = new string[0]; + string formattedContextString = null; + var control = false; + string defaultInprocHandler = null; + string defaultProgId = null; + string description = null; + string fileTypeMask = null; + string foreignServer = null; + string icon = null; + var iconIndex = CompilerConstants.IntegerNotSet; + string insertable = null; + string localFileServer = null; + var programmable = false; + var relativePath = YesNoType.NotSet; + var safeForInit = false; + var safeForScripting = false; + var shortServerPath = false; + string threadingModel = null; + string version = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Advertise": + classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "AppId": + appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Argument": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Context": + contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + break; + case "Control": + control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Handler": + defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "RelativePath": + relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + + // The following attributes result in rows always added to the Registry table rather than the Class table + case "Insertable": + insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable"; + break; + case "Programmable": + programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SafeForInitializing": + safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SafeForScripting": + safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ForeignServer": + foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Server": + localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortPath": + shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ThreadingModel": + threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Version": + version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == classId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var uniqueContexts = new HashSet(); + foreach (var context in contexts) + { + if (uniqueContexts.Contains(context)) + { + this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context)); + } + else + { + uniqueContexts.Add(context); + } + + if (context.EndsWith("32", StringComparison.Ordinal)) + { + class32bit = true; + } + else + { + class16bit = true; + } + } + + if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString())); + } + else + { + advertise = classAdvertise; + } + + // If the advertise state has not been set, default to non-advertised. + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + if (YesNoType.Yes == advertise && 0 == contexts.Length) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes")); + } + + if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName)); + } + + if (!String.IsNullOrEmpty(localFileServer)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer); + } + + // Local variables used strictly for child node processing. + var fileTypeMaskIndex = 0; + var firstProgIdForClass = YesNoType.Yes; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "FileTypeMask": + if (YesNoType.Yes == advertise) + { + fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child)); + } + else if (YesNoType.No == advertise) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.CreateRegistryRow(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId); + fileTypeMaskIndex++; + } + break; + case "Interface": + this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion); + break; + case "ProgId": + { + var foundExtension = false; + var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass); + if (null == defaultProgId) + { + defaultProgId = progId; + } + firstProgIdForClass = YesNoType.No; + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // If this Class is being advertised. + if (YesNoType.Yes == advertise) + { + if (null != fileServer || null != localFileServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes")); + } + + if (null != foreignServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes")); + } + + if (null == appId && null != parentAppId) + { + appId = parentAppId; + } + + // add a Class row for each context + if (!this.Core.EncounteredError) + { + foreach (var context in contexts) + { + var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers) + { + CLSID = classId, + Context = context, + ComponentRef = componentId, + DefaultProgIdRef = defaultProgId, + Description = description, + FileTypeMask = fileTypeMask, + DefInprocHandler = defaultInprocHandler, + Argument = argument, + FeatureRef = Guid.Empty.ToString("B"), + RelativePath = YesNoType.Yes == relativePath, + }); + + if (null != appId) + { + symbol.AppIdRef = appId; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId); + } + + if (null != icon) + { + symbol.IconRef = icon; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + } + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + symbol.IconIndex = iconIndex; + } + } + } + } + else if (YesNoType.No == advertise) + { + if (null == fileServer && null == localFileServer && null == foreignServer) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); + } + + if (null != fileServer && null != foreignServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File")); + } + else if (null != localFileServer && null != foreignServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); + } + else if (null == fileServer) + { + fileServer = localFileServer; + } + + if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no")); + } + + // add the core registry keys for each context in the class + foreach (var context in contexts) + { + if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server + { + if (null != argument) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context)); + } + + if (null != fileServer) + { + formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]"); + } + else if (null != foreignServer) + { + formattedContextString = foreignServer; + } + } + else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path) + { + if (null != fileServer) + { + if (shortServerPath) + { + formattedContextString = String.Concat("[!", fileServer, "]"); + } + else + { + formattedContextString = String.Concat("\"[#", fileServer, "]\""); + } + } + else if (null != foreignServer) + { + formattedContextString = foreignServer; + } + + if (null != argument) + { + formattedContextString = String.Concat(formattedContextString, " ", argument); + } + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32")); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context + + if (null != icon) // ClassId default icon + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); + + icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + icon = String.Concat(icon, ",", iconIndex); + } + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId); + } + } + + if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId); + } + + if (null != description) // ClassId description + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId); + } + + if (null != defaultInprocHandler) + { + switch (defaultInprocHandler) // ClassId Default Inproc Handler + { + case "1": + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); + break; + case "2": + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); + break; + case "3": + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); + break; + default: + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId); + break; + } + } + + if (YesNoType.NotSet != relativePath) // ClassId's RelativePath + { + this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers)); + } + } + + if (null != threadingModel) + { + threadingModel = Compiler.UppercaseFirstChar(threadingModel); + + // add a threading model for each context in the class + foreach (var context in contexts) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId); + } + } + + if (null != typeLibId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId); + } + + if (null != version) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId); + } + + if (null != insertable) + { + // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId); + } + + if (control) + { + // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId); + } + + if (programmable) + { + // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId); + } + + if (safeForInit) + { + this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); + } + + if (safeForScripting) + { + this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); + } + } + + /// + /// Parses an Interface element. + /// + /// Element to parse. + /// Identifier of parent component. + /// 16-bit proxy for interface. + /// 32-bit proxy for interface. + /// Optional TypeLib GUID for CLSID. + /// Version of the TypeLib to which this interface belongs. Required if typeLibId is specified + private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string baseInterface = null; + string interfaceId = null; + string name = null; + var numMethods = CompilerConstants.IntegerNotSet; + var versioned = true; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "BaseInterface": + baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "NumMethods": + numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "ProxyStubClassId": + proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib); + break; + case "ProxyStubClassId32": + proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Versioned": + versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == interfaceId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId); + if (null != typeLibId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId); + if (versioned) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId); + } + } + + if (null != baseInterface) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId); + } + + if (CompilerConstants.IntegerNotSet != numMethods) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId); + } + + if (null != proxyId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId); + } + + if (null != proxyId32) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId); + } + } + + /// + /// Parses a CLSID's file type mask element. + /// + /// Element to parse. + /// String representing the file type mask elements. + private string ParseFileTypeMaskElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var cb = 0; + var offset = CompilerConstants.IntegerNotSet; + string mask = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Mask": + mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Offset": + offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + + if (null == mask) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask")); + } + + if (CompilerConstants.IntegerNotSet == offset) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (mask.Length != value.Length) + { + this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers)); + } + cb = mask.Length / 2; + } + + return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value); + } + + /// + /// Parses a product search element. + /// + /// Element to parse. + /// + /// Signature for search element. + private void ParseProductSearchElement(XElement node, string propertyId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string upgradeCode = null; + string language = null; + string maximum = null; + string minimum = null; + var excludeLanguages = false; + var maxInclusive = false; + var minInclusive = true; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludeLanguages": + excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMaximum": + maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": + minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Language": + language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == minimum && null == maximum) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + VersionMin = minimum, + VersionMax = maximum, + Language = language, + ActionProperty = propertyId, + OnlyDetect = true, + ExcludeLanguages = excludeLanguages, + VersionMaxInclusive = maxInclusive, + VersionMinInclusive = minInclusive, + }); + } + } + + /// + /// Parses a registry search element. + /// + /// Element to parse. + /// Signature for search element. + private string ParseRegistrySearchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string name = null; + RegistryRootType? root = null; + RegLocatorType? type = null; + var search64bit = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + search64bit = false; + break; + case "always64": + search64bit = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "directory": + type = RegLocatorType.Directory; + break; + case "file": + type = RegLocatorType.FileName; + break; + case "raw": + type = RegLocatorType.Raw; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString()); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (!type.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + + var signature = id.Id; + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + + // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures + id = new Identifier(AccessModifier.Section, newId); + signature = null; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Type = type.Value, + Win64 = search64bit, + }); + } + + return signature; + } + + /// + /// Parses a registry search reference element. + /// + /// Element to parse. + /// Signature of referenced search element. + private string ParseRegistrySearchRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; // the id of the RegistrySearchRef element is its signature + } + + /// + /// Parses child elements for search signatures. + /// + /// Node whose children we are parsing. + /// Returns list of string signatures. + private List ParseSearchSignatures(XElement node) + { + var signatures = new List(); + + foreach (var child in node.Elements()) + { + string signature = null; + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComplianceDrive": + signature = this.ParseComplianceDriveElement(child); + break; + case "ComponentSearch": + signature = this.ParseComponentSearchElement(child); + break; + case "DirectorySearch": + signature = this.ParseDirectorySearchElement(child, String.Empty); + break; + case "DirectorySearchRef": + signature = this.ParseDirectorySearchRefElement(child, String.Empty); + break; + case "IniFileSearch": + signature = this.ParseIniFileSearchElement(child); + break; + case "ProductSearch": + // handled in ParsePropertyElement + break; + case "RegistrySearch": + signature = this.ParseRegistrySearchElement(child); + break; + case "RegistrySearchRef": + signature = this.ParseRegistrySearchRefElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + + + if (!String.IsNullOrEmpty(signature)) + { + signatures.Add(signature); + } + } + + return signatures; + } + + /// + /// Parses a compliance drive element. + /// + /// Element to parse. + /// Signature of nested search elements. + private string ParseComplianceDriveElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string signature = null; + + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE"); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == signature) + { + this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); + } + + return signature; + } + + /// + /// Parses a compilance check element. + /// + /// Element to parse. + private void ParseComplianceCheckElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + string signature = null; + + // see if this property is used for appSearch + var signatures = this.ParseSearchSignatures(node); + foreach (var sig in signatures) + { + // if we haven't picked a signature for this ComplianceCheck pick + // this one + if (null == signature) + { + signature = sig; + } + else if (signature != sig) + { + // all signatures under a ComplianceCheck must be the same + this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature)); + } + } + + if (null == signature) + { + this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature))); + } + } + + /// + /// Parses a component element. + /// + /// Element to parse. + /// Type of component's complex reference parent. Will be Uknown if there is no parent. + /// Optional identifier for component's primary parent. + /// Optional string for component's parent's language. + /// Optional disk id inherited from parent directory. + /// Optional identifier for component's directory. + /// Optional source path for files up to this point. + private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + var comPlusBits = CompilerConstants.IntegerNotSet; + string condition = null; + string subdirectory = null; + var encounteredODBCDataSource = false; + var files = 0; + var guid = "*"; + Identifier id = null; + string componentIdPlaceholder = null; + var keyFound = false; + string keyPath = null; + + var keyPathType = ComponentKeyPathType.Directory; + var location = ComponentLocation.LocalOnly; + var disableRegistryReflection = false; + + var neverOverwrite = false; + var permanent = false; + var shared = false; + var sharedDllRefCount = false; + var transitive = false; + var uninstallWhenSuperseded = false; + var win64 = this.Context.IsCurrentPlatform64Bit; + + var multiInstance = false; + var symbols = new List(); + string feature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "ComPlusFlags": + comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "DisableRegistryReflection": + disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Guid": + guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true); + break; + case "KeyPath": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + keyFound = true; + keyPath = null; + } + break; + case "Location": + var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (locationValue) + { + case "either": + location = ComponentLocation.Either; + break; + case "local": // this is the default + location = ComponentLocation.LocalOnly; + break; + case "source": + location = ComponentLocation.SourceOnly; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source")); + break; + } + break; + case "MultiInstance": + multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "NeverOverwrite": + neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Permanent": + permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Shared": + shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SharedDllRefCount": + sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Transitive": + transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "UninstallWhenSuperseded": + uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (id == null) + { + // Placeholder id for defaulting Component/@Id to keypath id. + componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd); + id = new Identifier(AccessModifier.Section, componentIdPlaceholder); + } + + if (String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); + } + + if (!String.IsNullOrEmpty(subdirectory)) + { + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); + } + + if (String.IsNullOrEmpty(guid) && shared) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", "")); + } + + if (String.IsNullOrEmpty(guid) && permanent) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", "")); + } + + if (null != feature) + { + if (this.compilingModule) + { + this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature")); + } + else + { + if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName)); + } + else + { + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); + } + } + } + + foreach (var child in node.Elements()) + { + var keyPathSet = YesNoType.NotSet; + string keyPossible = null; + ComponentKeyPathType? keyBit = null; + + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null); + break; + case "Category": + this.ParseCategoryElement(child, id.Id); + break; + case "Class": + this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null); + break; + case "CopyFile": + this.ParseCopyFileElement(child, id.Id, null); + break; + case "CreateFolder": + var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64); + break; + case "Environment": + this.ParseEnvironmentElement(child, id.Id); + break; + case "Extension": + this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null); + break; + case "File": + keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid); + keyBit = ComponentKeyPathType.File; + files++; + break; + case "IniFile": + this.ParseIniFileElement(child, id.Id); + break; + case "Interface": + this.ParseInterfaceElement(child, id.Id, null, null, null, null); + break; + case "IsolateComponent": + this.ParseIsolateComponentElement(child, id.Id); + break; + case "ODBCDataSource": + keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible); + keyBit = ComponentKeyPathType.OdbcDataSource; + encounteredODBCDataSource = true; + break; + case "ODBCDriver": + this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver); + break; + case "ODBCTranslator": + this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator); + break; + case "ProgId": + var foundExtension = false; + this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet); + break; + case "Provides": + if (win64) + { + this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id)); + } + + keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + + case "RegistryKey": + keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + case "RegistryValue": + keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + case "RemoveFile": + this.ParseRemoveFileElement(child, id.Id, directoryId); + break; + case "RemoveFolder": + this.ParseRemoveFolderElement(child, id.Id, directoryId); + break; + case "RemoveRegistryKey": + this.ParseRemoveRegistryKeyElement(child, id.Id); + break; + case "RemoveRegistryValue": + this.ParseRemoveRegistryValueElement(child, id.Id); + break; + case "ReserveCost": + this.ParseReserveCostElement(child, id.Id, directoryId); + break; + case "ServiceConfig": + this.ParseServiceConfigElement(child, id.Id, null); + break; + case "ServiceConfigFailureActions": + this.ParseServiceConfigFailureActionsElement(child, id.Id, null); + break; + case "ServiceControl": + this.ParseServiceControlElement(child, id.Id); + break; + case "ServiceInstall": + this.ParseServiceInstallElement(child, id.Id, win64); + break; + case "Shortcut": + this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No); + break; + case "SymbolPath": + symbols.Add(this.ParseSymbolPathElement(child)); + break; + case "TypeLib": + this.ParseTypeLibElement(child, id.Id, null, win64); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, }; + var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context); + if (null != possibleKeyPath) + { + if (PossibleKeyPathType.None == possibleKeyPath.Type) + { + keyPathSet = YesNoType.No; + } + else + { + keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet; + + if (!String.IsNullOrEmpty(possibleKeyPath.Id)) + { + keyPossible = possibleKeyPath.Id; + } + + if (PossibleKeyPathType.Registry == possibleKeyPath.Type || PossibleKeyPathType.RegistryFormatted == possibleKeyPath.Type) + { + keyBit = ComponentKeyPathType.Registry; //MsiInterop.MsidbComponentAttributesRegistryKeyPath; + } + } + } + } + + // Verify that either the key path is not set, or it is set along with a key path ID. + Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible)); + + if (keyFound && YesNoType.Yes == keyPathSet) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + // if a possible KeyPath has been found and that value was explicitly set as + // the KeyPath of the component, set it now. Alternatively, if a possible + // KeyPath has been found and no KeyPath has been previously set, use this + // value as the default KeyPath of the component + if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound))) + { + keyFound = YesNoType.Yes == keyPathSet; + keyPath = keyPossible; + keyPathType = keyBit.Value; + } + } + + // Check for conditions that exclude this component from using implicit ids and/or generated guids. + var allowImplicitIds = true; + if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) + { + allowImplicitIds = false; + if (guid == "*") + { + this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); + } + } + else if (0 < files && ComponentKeyPathType.Registry == keyPathType) + { + allowImplicitIds = false; + if (guid == "*") + { + this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); + } + } + + // Check for implicit KeyPath which can easily be accidentally changed + if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) + { + this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); + } + + // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath. + // either an explicit KeyPath="yes" attribute must be specified or requirements for + // generatable guid must be met. + if (componentIdPlaceholder == id.Id) + { + if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) + { + this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); + + id = new Identifier(AccessModifier.Section, keyPath); + } + else + { + this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers)); + } + } + + // finally add the Component table row + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) + { + ComponentId = guid, + DirectoryRef = directoryId, + Location = location, + Condition = condition, + KeyPath = keyPath, + KeyPathType = keyPathType, + DisableRegistryReflection = disableRegistryReflection, + NeverOverwrite = neverOverwrite, + Permanent = permanent, + SharedDllRefCount = sharedDllRefCount, + Shared = shared, + Transitive = transitive, + UninstallWhenSuperseded = uninstallWhenSuperseded, + Win64 = win64, + }); + + if (multiInstance) + { + this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + }); + } + + if (0 < symbols.Count) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id)) + { + SymbolType = SymbolPathType.Component, + SymbolId = id.Id, + SymbolPaths = String.Join(";", symbols), + }); + } + + // Complus + if (CompilerConstants.IntegerNotSet != comPlusBits) + { + this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers) + { + ComponentRef = id.Id, + ExpType = comPlusBits, + }); + } + + // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table + if (this.compilingModule) + { + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false); + } + else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that. + { + // If the Component is defined directly under a feature, then mark the complex reference primary. + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType); + } + } + } + + /// + /// Parses a component group element. + /// + /// Element to parse. + /// + /// + private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directoryId = null; + string subdirectory = null; + string source = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + source = String.Concat(source, Path.DirectorySeparatorChar); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id)); + + // Add this componentGroup and its parent in WixGroup. + this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id); + } + } + + /// + /// Parses a component group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element. + /// Identifier of parent element (usually a Feature or Module). + /// Optional language of parent (only useful for Modules). + private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) + { + Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary)); + } + + /// + /// Parses a component reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element. + /// Identifier of parent element (usually a Feature or Module). + /// Optional language of parent (only useful for Modules). + private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) + { + Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary)); + } + + /// + /// Parses a component search element. + /// + /// Element to parse. + /// Signature for search element. + private string ParseComponentSearchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string componentId = null; + var type = LocatorType.Filename; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Guid": + componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "directory": + type = LocatorType.Directory; + break; + case "file": + type = LocatorType.Filename; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("cmp", componentId, type.ToString()); + } + + var signature = id.Id; + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + + // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures + id = new Identifier(AccessModifier.Section, newId); + signature = null; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id) + { + SignatureRef = id.Id, + ComponentId = componentId, + Type = type, + }); + } + + return signature; + } + + /// + /// Parses a create folder element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Default identifier for directory to create. + /// true if the component is 64-bit. + /// Identifier for the directory that will be created + private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string subdirectory = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Shortcut": + this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); + break; + case "Permission": + this.ParsePermissionElement(child, directoryId, "CreateFolder"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, directoryId, "CreateFolder"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers) + { + DirectoryRef = directoryId, + ComponentRef = componentId, + }); + } + + return directoryId; + } + + /// + /// Parses a copy file element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of file to copy (null if moving the file). + private void ParseCopyFileElement(XElement node, string componentId, string fileId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var delete = false; + string destinationDirectory = null; + string destinationSubdirectory = null; + string destinationName = null; + string destinationShortName = null; + string destinationProperty = null; + string sourceDirectory = null; + string sourceSubdirectory = null; + string sourceFolder = null; + string sourceName = null; + string sourceProperty = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Delete": + delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "DestinationDirectory": + destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory); + break; + case "DestinationSubdirectory": + destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "DestinationName": + destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib); + break; + case "DestinationProperty": + destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "DestinationShortName": + destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib); + break; + case "FileId": + if (null != fileId) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); + } + fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId); + break; + case "SourceDirectory": + sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory); + break; + case "SourceSubdirectory": + sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "SourceName": + sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SourceProperty": + sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory")); + } + + if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty")); + } + + if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory")); + } + + sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory"); + + if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory")); + } + + destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory"); + + if (null == id) + { + id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName); + } + + this.Core.ParseForExtensionElements(node); + + if (null == fileId) + { + // DestinationDirectory or DestinationProperty must be specified + if (null == destinationDirectory && null == destinationProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + SourceName = sourceName, + DestinationName = destinationName, + DestinationShortName = destinationShortName, + SourceFolder = sourceDirectory ?? sourceProperty, + DestFolder = destinationDirectory ?? destinationProperty, + Delete = delete, + }); + } + } + else // copy the file + { + if (null != sourceDirectory) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId")); + } + + if (null != sourceFolder) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId")); + } + + if (null != sourceName) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId")); + } + + if (null != sourceProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId")); + } + + if (delete) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId")); + } + + if (null == destinationName && null == destinationDirectory && null == destinationProperty) + { + this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FileRef = fileId, + DestinationName = destinationName, + DestinationShortName = destinationShortName, + DestinationFolder = destinationDirectory ?? destinationProperty, + }); + } + } + } + + /// + /// Parses a CustomAction element. + /// + /// Element to parse. + private void ParseCustomActionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var inlineScript = false; + var suppressModularization = YesNoType.NotSet; + string source = null; + string target = null; + var explicitWin64 = false; + + string scriptFile = null; + string subdirectory = null; + + CustomActionSourceType? sourceType = null; + CustomActionTargetType? targetType = null; + var executionType = CustomActionExecutionType.Immediate; + var hidden = false; + var impersonate = true; + var patchUninstall = false; + var tsAware = false; + var win64 = false; + var async = false; + var ignoreResult = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "BinaryRef": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.Binary; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + explicitWin64 = true; + win64 = false; + break; + case "always64": + explicitWin64 = true; + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Directory": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.Directory; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source); + break; + case "DllEntry": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + targetType = CustomActionTargetType.Dll; + break; + case "Error": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.File; + targetType = CustomActionTargetType.TextData; + + // The target can be either a formatted error string or a literal + // error number. Try to convert to error number to determine whether + // to add a reference. No need to look at the value. + if (Int32.TryParse(target, out var ignored)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target); + } + break; + case "ExeCommand": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.Exe; + break; + case "Execute": + var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (execute) + { + case "commit": + executionType = CustomActionExecutionType.Commit; + break; + case "deferred": + executionType = CustomActionExecutionType.Deferred; + break; + case "firstSequence": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "immediate": + executionType = CustomActionExecutionType.Immediate; + break; + case "oncePerProcess": + executionType = CustomActionExecutionType.OncePerProcess; + break; + case "rollback": + executionType = CustomActionExecutionType.Rollback; + break; + case "secondSequence": + executionType = CustomActionExecutionType.ClientRepeat; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence")); + break; + } + break; + case "FileRef": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.File; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File + break; + case "HideTarget": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Impersonate": + impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "JScriptCall": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.JScript; + break; + case "PatchUninstall": + patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Property": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.Property; + break; + case "Return": + var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (returnValue) + { + case "asyncNoWait": + async = true; + ignoreResult = true; + break; + case "asyncWait": + async = true; + break; + case "check": + break; + case "ignore": + ignoreResult = true; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore")); + break; + } + break; + case "Script": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + + // set the source and target to empty string for error messages when the user sets multiple sources or targets + source = String.Empty; + target = String.Empty; + + inlineScript = true; + + var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (script) + { + case "jscript": + sourceType = CustomActionSourceType.Directory; + targetType = CustomActionTargetType.JScript; + break; + case "vbscript": + sourceType = CustomActionSourceType.Directory; + targetType = CustomActionTargetType.VBScript; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript")); + break; + } + break; + case "ScriptSourceFile": + scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TerminalServerAware": + tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.TextData; + break; + case "VBScriptCall": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.VBScript; + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) + { + win64 = true; + } + + if (!String.IsNullOrEmpty(subdirectory)) + { + if (sourceType == CustomActionSourceType.Directory) + { + source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory"); + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory")); + } + } + + // if we have an in-lined Script CustomAction ensure no source or target attributes were provided + if (inlineScript) + { + if (String.IsNullOrEmpty(scriptFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); + } + } + else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript + { + if (null == source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property")); + } + else if (CustomActionSourceType.Directory == sourceType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory")); + } + } + else if (CustomActionTargetType.JScript == targetType) // non-inline jscript + { + if (null == source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property")); + } + else if (CustomActionSourceType.Directory == sourceType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory")); + } + } + else if (CustomActionTargetType.Exe == targetType) // exe-command + { + if (null == source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property")); + } + } + else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); + } + + if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); + } + + if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall")); + } + + if (async && ignoreResult && CustomActionTargetType.Exe != targetType) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand")); + } + + // TS-aware CAs are valid only when deferred. + if (tsAware & + CustomActionExecutionType.Deferred != executionType && + CustomActionExecutionType.Rollback != executionType && + CustomActionExecutionType.Commit != executionType) + { + this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers)); + } + + // MSI doesn't support in-script property setting, so disallow it + if (CustomActionSourceType.Property == sourceType && + CustomActionTargetType.TextData == targetType && + (CustomActionExecutionType.Deferred == executionType || + CustomActionExecutionType.Rollback == executionType || + CustomActionExecutionType.Commit == executionType)) + { + this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers)); + } + + if (!targetType.HasValue /*0 == targetBits*/) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id) + { + ExecutionType = executionType, + Source = source, + SourceType = sourceType.Value, + Target = target, + TargetType = targetType.Value, + Async = async, + IgnoreResult = ignoreResult, + Impersonate = impersonate, + PatchUninstall = patchUninstall, + TSAware = tsAware, + Win64 = win64, + Hidden = hidden, + ScriptFile = new IntermediateFieldPathValue { Path = scriptFile } + }); + + if (YesNoType.Yes == suppressModularization) + { + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); + } + } + } + + /// + /// Parses a simple reference element. + /// + /// Element to parse. + /// Symbol which contains the target of the simple reference. + /// Id of the referenced element. + private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; + } + + /// + /// Parses a PatchFamilyRef element. + /// + /// Element to parse. + /// The parent type. + /// The ID of the parent. + /// Id of the referenced element. + private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var primaryKeys = new string[2]; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ProductCode": + primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == primaryKeys[0]) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchSequence, primaryKeys); + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true); + } + } + + /// + /// Parses an ensure table element. + /// + /// Element to parse. + private void ParseEnsureTableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (31 < id.Length) + { + this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id)); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.EnsureTable(sourceLineNumbers, id); + } + + /// + /// Parses a custom table element. + /// + /// Element to parse. + /// not cleaned + private void ParseCustomTableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string tableId = null; + var unreal = false; + var columns = new List(); + var foundColumns = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Unreal": + unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == tableId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (31 < tableId.Length) + { + this.Core.Write(ErrorMessages.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Column": + foundColumns = true; + + var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId); + if (column != null) + { + columns.Add(column); + } + break; + case "Row": + this.ParseRowElement(child, childSourceLineNumbers, tableId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (columns.Count > 0) + { + if (!columns.Where(c => c.PrimaryKey).Any()) + { + this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); + } + + if (!this.Core.EncounteredError) + { + var columnNames = String.Join(new string(WixCustomTableSymbol.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); + + this.Core.AddSymbol(new WixCustomTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, tableId)) + { + ColumnNames = columnNames, + Unreal = unreal, + }); + } + else if (!foundColumns) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Column")); + } + } + } + + /// + /// Parses a CustomTableRef element. + /// + /// Element to parse. + private void ParseCustomTableRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string tableId = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == tableId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Row": + this.ParseRowElement(child, childSourceLineNumbers, tableId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a Column element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// Table Id. + private WixCustomTableColumnSymbol ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId) + { + string columnName = null; + IntermediateFieldType? columnType = null; + var description = String.Empty; + int? keyColumn = null; + var keyTable = String.Empty; + var localizable = false; + long? maxValue = null; + long? minValue = null; + WixCustomTableColumnCategoryType? category = null; + var modularization = WixCustomTableColumnModularizeType.None; + var nullable = false; + var primaryKey = false; + var setValues = String.Empty; + var columnUnreal = false; + var width = 0; + + foreach (var childAttrib in child.Attributes()) + { + switch (childAttrib.Name.LocalName) + { + case "Id": + columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); + break; + case "Category": + var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (categoryValue) + { + case "text": + category = WixCustomTableColumnCategoryType.Text; + break; + case "upperCase": + category = WixCustomTableColumnCategoryType.UpperCase; + break; + case "lowerCase": + category = WixCustomTableColumnCategoryType.LowerCase; + break; + case "integer": + category = WixCustomTableColumnCategoryType.Integer; + break; + case "doubleInteger": + category = WixCustomTableColumnCategoryType.DoubleInteger; + break; + case "timeDate": + category = WixCustomTableColumnCategoryType.TimeDate; + break; + case "identifier": + category = WixCustomTableColumnCategoryType.Identifier; + break; + case "property": + category = WixCustomTableColumnCategoryType.Property; + break; + case "filename": + category = WixCustomTableColumnCategoryType.Filename; + break; + case "wildCardFilename": + category = WixCustomTableColumnCategoryType.WildCardFilename; + break; + case "path": + category = WixCustomTableColumnCategoryType.Path; + break; + case "paths": + category = WixCustomTableColumnCategoryType.Paths; + break; + case "anyPath": + category = WixCustomTableColumnCategoryType.AnyPath; + break; + case "defaultDir": + category = WixCustomTableColumnCategoryType.DefaultDir; + break; + case "regPath": + category = WixCustomTableColumnCategoryType.RegPath; + break; + case "formatted": + category = WixCustomTableColumnCategoryType.Formatted; + break; + case "formattedSddl": + category = WixCustomTableColumnCategoryType.FormattedSddl; + break; + case "template": + category = WixCustomTableColumnCategoryType.Template; + break; + case "condition": + category = WixCustomTableColumnCategoryType.Condition; + break; + case "guid": + category = WixCustomTableColumnCategoryType.Guid; + break; + case "version": + category = WixCustomTableColumnCategoryType.Version; + break; + case "language": + category = WixCustomTableColumnCategoryType.Language; + break; + case "binary": + category = WixCustomTableColumnCategoryType.Binary; + break; + case "customSource": + category = WixCustomTableColumnCategoryType.CustomSource; + break; + case "cabinet": + category = WixCustomTableColumnCategoryType.Cabinet; + break; + case "shortcut": + category = WixCustomTableColumnCategoryType.Shortcut; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, + "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", + "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", + "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } + break; + case "Description": + description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "KeyColumn": + keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); + break; + case "KeyTable": + keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "Localizable": + localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "MaxValue": + maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + break; + case "MinValue": + minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + break; + case "Modularize": + var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (modularizeValue) + { + case "column": + modularization = WixCustomTableColumnModularizeType.Column; + break; + case "companionFile": + modularization = WixCustomTableColumnModularizeType.CompanionFile; + break; + case "condition": + modularization = WixCustomTableColumnModularizeType.Condition; + break; + case "controlEventArgument": + modularization = WixCustomTableColumnModularizeType.ControlEventArgument; + break; + case "controlText": + modularization = WixCustomTableColumnModularizeType.ControlText; + break; + case "icon": + modularization = WixCustomTableColumnModularizeType.Icon; + break; + case "none": + modularization = WixCustomTableColumnModularizeType.None; + break; + case "property": + modularization = WixCustomTableColumnModularizeType.Property; + break; + case "semicolonDelimited": + modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } + break; + case "Nullable": + nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "PrimaryKey": + primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "Set": + setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (typeValue) + { + case "binary": + columnType = IntermediateFieldType.Path; + break; + case "int": + columnType = IntermediateFieldType.Number; + break; + case "string": + columnType = IntermediateFieldType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } + break; + case "Width": + width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); + break; + case "Unreal": + columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + default: + this.Core.UnexpectedAttribute(child, childAttrib); + break; + } + } + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); + } + + if (!columnType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); + } + else if (columnType == IntermediateFieldType.Number) + { + if (2 != width && 4 != width) + { + this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); + } + } + else if (columnType == IntermediateFieldType.Path) + { + if (!category.HasValue) + { + category = WixCustomTableColumnCategoryType.Binary; + } + else if (category != WixCustomTableColumnCategoryType.Binary) + { + this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); + } + } + + this.Core.ParseForExtensionElements(child); + + if (this.Core.EncounteredError) + { + return null; + } + + var attributes = primaryKey ? WixCustomTableColumnSymbolAttributes.PrimaryKey : WixCustomTableColumnSymbolAttributes.None; + attributes |= localizable ? WixCustomTableColumnSymbolAttributes.Localizable : WixCustomTableColumnSymbolAttributes.None; + attributes |= nullable ? WixCustomTableColumnSymbolAttributes.Nullable : WixCustomTableColumnSymbolAttributes.None; + attributes |= columnUnreal ? WixCustomTableColumnSymbolAttributes.Unreal : WixCustomTableColumnSymbolAttributes.None; + + var column = this.Core.AddSymbol(new WixCustomTableColumnSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, columnName)) + { + TableRef = tableId, + Name = columnName, + Type = columnType.Value, + Attributes = attributes, + Width = width, + Category = category, + Description = description, + KeyColumn = keyColumn, + KeyTable = keyTable, + MaxValue = maxValue, + MinValue = minValue, + Modularize = modularization, + Set = setValues, + }); + return column; + } + + /// + /// Parses a Row element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// Table Id. + private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId) + { + var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Data": + string columnName = null; + string data = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Column": + columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + this.Core.InnerTextDisallowed(node); + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixCustomTableCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, rowId, columnName)) + { + RowId = rowId, + ColumnRef = columnName, + TableRef = tableId, + Data = data + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); + } + } + + /// + /// Parses a directory element. + /// + /// Element to parse. + /// Optional identifier of parent directory. + /// Disk id inherited from parent directory. + /// Path to source file as of yet. + private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string componentGuidGenerationSeed = null; + var fileSourceAttribSet = false; + XAttribute nameAttribute = null; + var name = "."; // default to parent directory. + string shortName = null; + string sourceName = null; + string shortSourceName = null; + string symbols = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ComponentGuidGenerationSeed": + componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "FileSource": + fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + fileSourceAttribSet = true; + break; + case "Name": + if ("." == attrib.Value) + { + name = attrib.Value; + } + else + { + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + } + nameAttribute = attrib; + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "ShortSourceName": + shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "SourceName": + if ("." == attrib.Value) + { + sourceName = attrib.Value; + } + else + { + sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (nameAttribute == null) + { + if (!String.IsNullOrEmpty(shortName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); + } + } + else if (!String.IsNullOrEmpty(name)) + { + if (String.IsNullOrEmpty(shortName)) + { + } + else if (name == ".") + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name)); + } + else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name)); + } + } + + if (String.IsNullOrEmpty(sourceName)) + { + if (!String.IsNullOrEmpty(shortSourceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName")); + } + } + else + { + if (String.IsNullOrEmpty(shortSourceName)) + { + } + else if (sourceName == ".") + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName)); + } + else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName)); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); + } + else if (WindowsInstallerStandard.IsStandardDirectory(id.Id)) + { + if (String.IsNullOrEmpty(sourceName)) + { + this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id)); + } + + if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null) + { + this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); + } + } + + // Update the file source path appropriately. + if (fileSourceAttribSet) + { + if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); + } + } + else // add the appropriate part of this directory element to the file source. + { + string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; + + if (!String.IsNullOrEmpty(append)) + { + fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource); + break; + case "Directory": + this.ParseDirectoryElement(child, id.Id, diskId, fileSource); + break; + case "Merge": + this.ParseMergeElement(child, id.Id, diskId); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + ParentDirectoryRef = parentId, + Name = name, + ShortName = shortName, + SourceName = sourceName, + SourceShortName = shortSourceName, + ComponentGuidGenerationSeed = componentGuidGenerationSeed + }); + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id) + { + SymbolType = SymbolPathType.Directory, + SymbolId = id.Id, + SymbolPaths = symbols, + }); + } + } + } + + /// + /// Parses a directory reference element. + /// + /// Element to parse. + private void ParseDirectoryRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var diskId = CompilerConstants.IntegerNotSet; + var fileSource = String.Empty; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "FileSource": + fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id)); + } + + if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource); + break; + case "Directory": + this.ParseDirectoryElement(child, id, diskId, fileSource); + break; + case "Merge": + this.ParseMergeElement(child, id, diskId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a directory search element. + /// + /// Element to parse. + /// Signature of parent search element. + /// Signature of search element. + private string ParseDirectorySearchElement(XElement node, string parentSignature) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var depth = CompilerConstants.IntegerNotSet; + string path = null; + var assignToProperty = false; + string signature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Depth": + depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Path": + path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "AssignToProperty": + assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("dir", path, depth.ToString()); + } + + signature = id.Id; + + var oneChild = false; + var hasFileSearch = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + hasFileSearch = true; + signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth); + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + + // If AssignToProperty is set, only a FileSearch + // or no child element can be nested. + if (assignToProperty) + { + if (!hasFileSearch) + { + this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName)); + } + else if (!oneChild) + { + // This a normal directory search. + assignToProperty = false; + } + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var access = id.Access; + var rowId = id.Id; + + // If AssignToProperty is set, the DrLocator row created by + // ParseFileSearchElement creates the directory entry to return + // and the row created here is for the file search. + if (assignToProperty) + { + access = AccessModifier.Section; + rowId = signature; + + // The property should be set to the directory search Id. + signature = id.Id; + } + + var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path)) + { + SignatureRef = rowId, + Parent = parentSignature, + Path = path, + }); + + if (CompilerConstants.IntegerNotSet != depth) + { + symbol.Depth = depth; + } + } + + return signature; + } + + /// + /// Parses a directory search reference element. + /// + /// Element to parse. + /// Signature of parent search element. + /// Signature of search element. + private string ParseDirectorySearchRefElement(XElement node, string parentSignature) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + Identifier parent = null; + string path = null; + string signature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Parent": + parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Path": + path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != parent) + { + if (!String.IsNullOrEmpty(parentSignature)) + { + this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature)); + } + else + { + parentSignature = parent.Id; + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("dsr", parentSignature, path); + } + + signature = id.Id; + + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path); + + return signature; + } + + /// + /// Parses a feature element. + /// + /// Element to parse. + /// The type of parent. + /// Optional identifer for parent feature. + /// Display value for last feature used to get the features to display in the same order as specified + /// in the source code. + private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string configurableDirectory = null; + string description = null; + var displayValue = "collapse"; + var level = 1; + string title = null; + + var installDefault = FeatureInstallDefault.Local; + var typicalDefault = FeatureTypicalDefault.Install; + var disallowAbsent = false; + var disallowAdvertise = false; + var display = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "AllowAbsent": + disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); + break; + case "AllowAdvertise": + disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); + break; + case "ConfigurableDirectory": + configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Display": + displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallDefault": + var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installDefaultValue) + { + case "followParent": + if (ComplexReferenceParentType.Product == parentType) + { + this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers)); + } + //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent; + installDefault = FeatureInstallDefault.FollowParent; + break; + case "local": // this is the default + installDefault = FeatureInstallDefault.Local; + break; + case "source": + //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource; + installDefault = FeatureInstallDefault.Source; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source")); + break; + } + break; + case "Level": + level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Title": + title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-FEATURE-TITLE-HERE" == title) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title)); + } + break; + case "TypicalDefault": + var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typicalValue) + { + case "advertise": + //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise; + typicalDefault = FeatureTypicalDefault.Advertise; + break; + case "install": // this is the default + typicalDefault = FeatureTypicalDefault.Install; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (38 < id.Id.Length) + { + this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + + if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory) + { + this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory)); + } + + if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise) + { + this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no")); + } + + var childDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id); + break; + case "Level": + this.ParseLevelElement(child, id.Id); + break; + case "MergeRef": + this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + switch (displayValue) + { + case "collapse": + lastDisplay = (lastDisplay | 1) + 1; + display = lastDisplay; + break; + case "expand": + lastDisplay = (lastDisplay + 1) | 1; + display = lastDisplay; + break; + case "hidden": + display = 0; + break; + default: + if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden")); + } + else + { + // Save the display value (if its not hidden) for subsequent rows + if (0 != display) + { + lastDisplay = display; + } + } + break; + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id) + { + ParentFeatureRef = null, // this field is set in the linker + Title = title, + Description = description, + Display = display, + Level = level, + DirectoryRef = configurableDirectory, + DisallowAbsent = disallowAbsent, + DisallowAdvertise = disallowAdvertise, + InstallDefault = installDefault, + TypicalDefault = typicalDefault, + }); + + if (ComplexReferenceParentType.Unknown != parentType) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false); + } + } + } + + /// + /// Parses a feature reference element. + /// + /// Element to parse. + /// The type of parent. + /// Optional identifier for parent feature. + private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var ignoreParent = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id); + break; + case "IgnoreParent": + ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var lastDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay); + break; + case "FeatureGroup": + this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); + break; + case "MergeRef": + this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false); + } + } + } + + /// + /// Parses a feature group element. + /// + /// Element to parse. + /// + /// + private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + var lastDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); + break; + case "MergeRef": + this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id)); + + //Add this FeatureGroup and its parent in WixGroup. + this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id); + } + } + + /// + /// Parses a feature group reference element. + /// + /// Element to parse. + /// The type of parent. + /// Identifier of parent element. + private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var ignoreParent = YesNoType.NotSet; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id); + break; + case "IgnoreParent": + ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (YesNoType.Yes != ignoreParent) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary)); + } + } + } + + /// + /// Parses an environment element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseEnvironmentElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + EnvironmentActionType? action = null; + EnvironmentPartType? part = null; + var permanent = false; + var separator = ";"; // default to ';' + var system = false; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "create": + action = EnvironmentActionType.Create; + break; + case "set": + action = EnvironmentActionType.Set; + break; + case "remove": + action = EnvironmentActionType.Remove; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove")); + break; + } + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Part": + var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (partValue) + { + case "all": + part = EnvironmentPartType.All; + break; + case "first": + part = EnvironmentPartType.First; + break; + case "last": + part = EnvironmentPartType.Last; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last")); + break; + } + break; + case "Permanent": + permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Separator": + separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "System": + system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString()); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (part.HasValue && action == EnvironmentActionType.Create) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); + } + + //if (Wix.Environment.PartType.NotSet != partType) + //{ + // if ("+" == action) + // { + // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); + // } + + // switch (partType) + // { + // case Wix.Environment.PartType.all: + // break; + // case Wix.Environment.PartType.first: + // text = String.Concat(text, separator, "[~]"); + // break; + // case Wix.Environment.PartType.last: + // text = String.Concat("[~]", separator, text); + // break; + // } + //} + + //if (permanent) + //{ + // uninstall = null; + //} + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id) + { + Name = name, + Value = value, + Separator = separator, + Action = action, + Part = part, + Permanent = permanent, + System = system, + ComponentRef = componentId + }); + } + } + + /// + /// Parses an error element. + /// + /// Element to parse. + private void ParseErrorElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var id = CompilerConstants.IntegerNotSet; + string message = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = CompilerConstants.IllegalInteger; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) + { + Message = message + }); + } + } + + /// + /// Parses an extension element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Flag if this extension is advertised. + /// ProgId for extension. + private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string extension = null; + string mime = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Advertise": + var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString())); + } + advertise = extensionAdvertise; + break; + case "ContentType": + mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + var context = new Dictionary() { { "ProgId", progId }, { "ComponentId", componentId } }; + this.Core.ParseExtensionAttribute(node, attrib, context); + } + } + + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Verb": + this.ParseVerbElement(child, extension, progId, componentId, advertise); + break; + case "MIME": + var newMime = this.ParseMIMEElement(child, extension, componentId, advertise); + if (null != newMime && null == mime) + { + mime = newMime; + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (YesNoType.Yes == advertise) + { + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ExtensionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, extension, componentId)) + { + Extension = extension, + ComponentRef = componentId, + ProgIdRef = progId, + MimeRef = mime, + FeatureRef = Guid.Empty.ToString("B"), + }); + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Verb); + } + } + else if (YesNoType.No == advertise) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension + if (null != mime) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType + } + } + } + + + /// + /// Parses a file element. + /// + /// File element to parse. + /// Parent's component id. + /// Ancestor's directory id. + /// Disk id inherited from parent component. + /// Default source path of parent directory. + /// This will be set with the possible keyPath for the parent component. + /// true if the component is 64-bit. + /// + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var assemblyType = AssemblyType.NotAnAssembly; + string assemblyApplication = null; + string assemblyManifest = null; + string bindPath = null; + + //int bits = MsiInterop.MsidbFileAttributesVital; + var readOnly = false; + var checksum = false; + bool? compressed = null; + var hidden = false; + var system = false; + var vital = true; // assume all files are vital. + + string companionFile = null; + string defaultLanguage = null; + var defaultSize = 0; + string defaultVersion = null; + string fontTitle = null; + var keyPath = YesNoType.NotSet; + string name = null; + var patchGroup = CompilerConstants.IntegerNotSet; + var patchIgnore = false; + var patchIncludeWholeFile = false; + var patchAllowIgnoreOnError = false; + + string ignoreLengths = null; + string ignoreOffsets = null; + string protectLengths = null; + string protectOffsets = null; + string symbols = null; + + string procArch = null; + int? selfRegCost = null; + string shortName = null; + var source = sourcePath; // assume we'll use the parents as the source for this file + var sourceSet = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Assembly": + var assemblyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (assemblyValue) + { + case ".net": + assemblyType = AssemblyType.DotNetAssembly; + break; + case "no": + assemblyType = AssemblyType.NotAnAssembly; + break; + case "win32": + assemblyType = AssemblyType.Win32Assembly; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net")); + break; + } + break; + case "AssemblyApplication": + assemblyApplication = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyApplication); + break; + case "AssemblyManifest": + assemblyManifest = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyManifest); + break; + case "BindPath": + bindPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Checksum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + checksum = true; + //bits |= MsiInterop.MsidbFileAttributesChecksum; + } + break; + case "CompanionFile": + companionFile = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, companionFile); + break; + case "Compressed": + var compressedValue = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + if (YesNoDefaultType.Yes == compressedValue) + { + compressed = true; + //bits |= MsiInterop.MsidbFileAttributesCompressed; + } + else if (YesNoDefaultType.No == compressedValue) + { + compressed = false; + //bits |= MsiInterop.MsidbFileAttributesNoncompressed; + } + break; + case "DefaultLanguage": + defaultLanguage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DefaultSize": + defaultSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "DefaultVersion": + defaultVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "FontTitle": + fontTitle = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + hidden = true; + //bits |= MsiInterop.MsidbFileAttributesHidden; + } + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "PatchGroup": + patchGroup = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); + break; + case "PatchIgnore": + patchIgnore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "PatchWholeFile": + patchIncludeWholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "PatchAllowIgnoreOnError": + patchAllowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ProcessorArchitecture": + var procArchValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (procArchValue) + { + case "msil": + procArch = "MSIL"; + break; + case "x86": + procArch = "x86"; + break; + case "x64": + procArch = "amd64"; + break; + case "arm64": + procArch = "arm64"; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64")); + break; + } + break; + case "ReadOnly": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + readOnly = true; + //bits |= MsiInterop.MsidbFileAttributesReadOnly; + } + break; + case "SelfRegCost": + selfRegCost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + sourceSet = true; + break; + case "System": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + system = true; + //bits |= MsiInterop.MsidbFileAttributesSystem; + } + break; + case "TrueType": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + fontTitle = String.Empty; + } + break; + case "Vital": + var isVital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (YesNoType.Yes == isVital) + { + vital = true; + //bits |= MsiInterop.MsidbFileAttributesVital; + } + else if (YesNoType.No == isVital) + { + vital = false; + //bits &= ~MsiInterop.MsidbFileAttributesVital; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != companionFile) + { + // the companion file cannot be the key path of a component + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes")); + } + } + + if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name) + { + name = Path.GetFileName(source); + if (!this.Core.IsValidLongFilename(name, false)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); + } + } + + if (name == null) + { + if (shortName == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else + { + name = shortName; + shortName = null; + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("fil", directoryId, name); + } + + if (null != defaultVersion && null != companionFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); + } + + if (AssemblyType.NotAnAssembly == assemblyType) + { + if (null != assemblyManifest) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest")); + } + + if (null != assemblyApplication) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication")); + } + } + else + { + if (AssemblyType.Win32Assembly == assemblyType && null == assemblyManifest) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32")); + } + + // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath + if (YesNoType.Yes != keyPath && "*" != componentGuid) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (AssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes")); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); + break; + case "AssemblyName": + this.ParseAssemblyName(child, componentId); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); + break; + case "CopyFile": + this.ParseCopyFileElement(child, componentId, id.Id); + break; + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ODBCDriver": + this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); + break; + case "ODBCTranslator": + this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "File"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "File"); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "Shortcut": + this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "TypeLib": + this.ParseTypeLibElement(child, componentId, id.Id, win64Component); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError) + { + var patchAttributes = PatchAttributeType.None; + if (patchIgnore) + { + patchAttributes |= PatchAttributeType.Ignore; + } + if (patchIncludeWholeFile) + { + patchAttributes |= PatchAttributeType.IncludeWholeFile; + } + if (patchAllowIgnoreOnError) + { + patchAttributes |= PatchAttributeType.AllowIgnoreOnError; + } + + if (String.IsNullOrEmpty(source)) + { + source = name; + } + else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name + { + source = Path.Combine(source, name); + } + + var attributes = FileSymbolAttributes.None; + attributes |= readOnly ? FileSymbolAttributes.ReadOnly : 0; + attributes |= hidden ? FileSymbolAttributes.Hidden : 0; + attributes |= system ? FileSymbolAttributes.System : 0; + attributes |= vital ? FileSymbolAttributes.Vital : 0; + attributes |= checksum ? FileSymbolAttributes.Checksum : 0; + attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0; + attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0; + + this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Name = name, + ShortName = shortName, + FileSize = defaultSize, + Version = companionFile ?? defaultVersion, + Language = defaultLanguage, + Attributes = attributes, + + DirectoryRef = directoryId, + DiskId = (CompilerConstants.IntegerNotSet == diskId) ? null : (int?)diskId, + Source = new IntermediateFieldPathValue { Path = source }, + + FontTitle = fontTitle, + SelfRegCost = selfRegCost, + BindPath = bindPath, + + PatchGroup = (CompilerConstants.IntegerNotSet == patchGroup) ? null : (int?)patchGroup, + PatchAttributes = patchAttributes, + + // Delta patching information + RetainLengths = protectLengths, + IgnoreOffsets = ignoreOffsets, + IgnoreLengths = ignoreLengths, + RetainOffsets = protectOffsets, + SymbolPaths = symbols, + }); + + if (AssemblyType.NotAnAssembly != assemblyType) + { + this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FeatureRef = Guid.Empty.ToString("B"), + ManifestFileRef = assemblyManifest, + ApplicationFileRef = assemblyApplication, + Type = assemblyType, + ProcessorArchitecture = procArch, + }); + } + } + + if (CompilerConstants.IntegerNotSet != diskId) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + + // If this component does not have a companion file this file is a possible keypath. + possibleKeyPath = null; + if (null == companionFile) + { + possibleKeyPath = id.Id; + } + + return keyPath; + } + + /// + /// Parses a file search element. + /// + /// Element to parse. + /// Signature of parent search element. + /// Whether this search element is used to search for the parent directory. + /// The depth specified by the parent search element. + /// Signature of search element. + private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string languages = null; + var minDate = CompilerConstants.IntegerNotSet; + var maxDate = CompilerConstants.IntegerNotSet; + var maxSize = CompilerConstants.IntegerNotSet; + var minSize = CompilerConstants.IntegerNotSet; + string maxVersion = null; + string minVersion = null; + string name = null; + string shortName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "MinVersion": + minVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MaxVersion": + maxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MinSize": + minSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "MaxSize": + maxSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "MinDate": + minDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); + break; + case "MaxDate": + maxDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); + break; + case "Languages": + languages = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Using both ShortName and Name will not always work due to a Windows Installer bug. + if (null != shortName && null != name) + { + this.Core.Write(WarningMessages.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); + } + else if (null == shortName && null == name) // at least one name must be specified. + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (this.Core.IsValidShortFilename(name, false)) + { + if (null == shortName) + { + shortName = name; + name = null; + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); + } + } + + if (null == id) + { + if (String.IsNullOrEmpty(parentSignature)) + { + id = this.Core.CreateIdentifier("fs", name ?? shortName); + } + else // reuse parent signature in the Signature table + { + id = new Identifier(AccessModifier.Section, parentSignature); + } + } + + var isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal); + if (parentDirectorySearch) + { + // If searching for the parent directory, the Id attribute + // value must be specified and unique. + if (isSameId) + { + this.Core.Write(ErrorMessages.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName)); + } + } + else if (parentDepth > 1) + { + // Otherwise, if the depth > 1 the Id must be absent or the same + // as the parent DirectorySearch if AssignToProperty is not set. + if (!isSameId) + { + this.Core.Write(ErrorMessages.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature)); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new SignatureSymbol(sourceLineNumbers, id) + { + FileName = name ?? shortName, + MinVersion = minVersion, + MaxVersion = maxVersion, + Languages = languages + }); + + if (CompilerConstants.IntegerNotSet != minSize) + { + symbol.MinSize = minSize; + } + + if (CompilerConstants.IntegerNotSet != maxSize) + { + symbol.MaxSize = maxSize; + } + + if (CompilerConstants.IntegerNotSet != minDate) + { + symbol.MinDate = minDate; + } + + if (CompilerConstants.IntegerNotSet != maxDate) + { + symbol.MaxDate = maxDate; + } + + // Create a DrLocator row to associate the file with a directory + // when a different identifier is specified for the FileSearch. + if (!isSameId) + { + if (parentDirectorySearch) + { + // Creates the DrLocator row for the directory search while + // the parent DirectorySearch creates the file locator row. + this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, parentSignature, id.Id, String.Empty)) + { + SignatureRef = parentSignature, + Parent = id.Id + }); + } + else + { + this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id.Id, parentSignature, String.Empty)) + { + SignatureRef = id.Id, + Parent = parentSignature + }); + } + } + } + + return id.Id; // the id of the FileSearch element is its signature + } + + + /// + /// Parses a fragment element. + /// + /// Element to parse. + private void ParseFragmentElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. + + this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId); + + var featureDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "_locDefinition": + break; + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "BootstrapperApplication": + this.ParseBootstrapperApplicationElement(child); + break; + case "BootstrapperApplicationRef": + this.ParseBootstrapperApplicationRefElement(child); + break; + case "BundleCustomData": + this.ParseBundleCustomDataElement(child); + break; + case "BundleCustomDataRef": + this.ParseBundleCustomDataRefElement(child); + break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); + break; + case "ComplianceCheck": + this.ParseComplianceCheckElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroup": + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); + break; + case "Container": + this.ParseContainerElement(child); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay); + break; + case "FeatureGroup": + this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "Media": + this.ParseMediaElement(child, null); + break; + case "MediaTemplate": + this.ParseMediaTemplateElement(child, null); + break; + case "Launch": + this.ParseLaunchElement(child); + break; + case "PackageGroup": + this.ParsePackageGroupElement(child); + break; + case "PackageCertificates": + case "PatchCertificates": + this.ParseCertificatesElement(child); + break; + case "PatchFamily": + this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id); + break; + case "PatchFamilyGroup": + this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id); + break; + case "PatchFamilyGroupRef": + this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id); + break; + case "PayloadGroup": + this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "RelatedBundle": + this.ParseRelatedBundleElement(child); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SetVariable": + this.ParseSetVariableElement(child); + break; + case "SetVariableRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "Upgrade": + this.ParseUpgradeElement(child); + break; + case "Variable": + this.ParseVariableElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError && null != id) + { + this.Core.AddSymbol(new WixFragmentSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a launch condition element. + /// + /// Element to parse. + private void ParseLaunchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + string message = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(condition)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); + } + + if (String.IsNullOrEmpty(message)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); + } + + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) + { + Condition = condition, + Description = message + }); + } + } + + /// + /// Parses a IniFile element. + /// + /// Element to parse. + /// Identifier of the parent component. + private void ParseIniFileElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + IniFileActionType? action = null; + string directory = null; + string key = null; + string name = null; + string section = null; + string shortName = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "addLine": + action = IniFileActionType.AddLine; + break; + case "addTag": + action = IniFileActionType.AddTag; + break; + case "removeLine": + action = IniFileActionType.RemoveLine; + break; + case "removeTag": + action = IniFileActionType.RemoveTag; + break; + case "": // error case handled by GetAttributeValue() + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag")); + break; + } + break; + case "Directory": + directory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Section": + section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!action.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + else if (IniFileActionType.AddLine == action || IniFileActionType.AddTag == action || IniFileActionType.CreateLine == action) + { + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == section) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new IniFileSymbol(sourceLineNumbers, id) + { + FileName = name, + ShortFileName = shortName, + DirProperty = directory, + Section = section, + Key = key, + Value = value, + Action = action.Value, + ComponentRef = componentId + }); + } + } + + /// + /// Parses an IniFile search element. + /// + /// Element to parse. + /// Signature for search element. + private string ParseIniFileSearchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var field = CompilerConstants.IntegerNotSet; + string key = null; + string name = null; + string section = null; + string shortName = null; + string signature = null; + var type = 1; // default is file + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Field": + field = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Section": + section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "directory": + type = 0; + break; + case "file": + type = 1; + break; + case "raw": + type = 2; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == section) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString()); + } + + signature = id.Id; + + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + + // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures + id = new Identifier(AccessModifier.Section, newId); + signature = null; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new IniLocatorSymbol(sourceLineNumbers, id) + { + FileName = name, + ShortFileName = shortName, + Section = section, + Key = key, + Type = type + }); + + if (CompilerConstants.IntegerNotSet != field) + { + symbol.Field = field; + } + } + + return signature; + } + + /// + /// Parses an isolated component element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseIsolateComponentElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string shared = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Shared": + shared = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, shared); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == shared) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new IsolatedComponentSymbol(sourceLineNumbers) + { + SharedComponentRef = shared, + ApplicationComponentRef = componentId + }); + } + } + + /// + /// Parses a PatchCertificates or PackageCertificates element. + /// + /// The element to parse. + private void ParseCertificatesElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + // no attributes are supported for this element + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + this.Core.UnexpectedAttribute(node, attrib); + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DigitalCertificate": + var name = this.ParseDigitalCertificateElement(child); + + if (!this.Core.EncounteredError) + { + if ("PatchCertificates" == node.Name.LocalName) + { + this.Core.AddSymbol(new MsiPatchCertificateSymbol(sourceLineNumbers) + { + PatchCertificate = name, + DigitalCertificateRef = name, + }); + } + else + { + this.Core.AddSymbol(new MsiPackageCertificateSymbol(sourceLineNumbers) + { + PackageCertificate = name, + DigitalCertificateRef = name, + }); + } + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses an digital certificate element. + /// + /// Element to parse. + /// The identifier of the certificate. + private string ParseDigitalCertificateElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (40 < id.Id.Length) + { + this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40)); + + // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate + // currently have no usage in merge modules. + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiDigitalCertificateSymbol(sourceLineNumbers, id) + { + CertData = sourceFile + }); + } + + return id.Id; + } + + /// + /// Parses an digital signature element. + /// + /// Element to parse. + /// Disk id inherited from parent media. + private void ParseDigitalSignatureElement(XElement node, string diskId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string certificateId = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // sanity check for debug to ensure the stream name will not be a problem + if (null != sourceFile) + { + Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DigitalCertificate": + certificateId = this.ParseDigitalCertificateElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == certificateId) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiDigitalSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "Media", diskId)) + { + Table = "Media", + SignObject = diskId, + DigitalCertificateRef = certificateId, + Hash = sourceFile + }); + } + } + + /// + /// Parses a MajorUpgrade element. + /// + /// The element to parse. + /// The current context. + private void ParseMajorUpgradeElement(XElement node, IDictionary contextValues) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var migrateFeatures = true; + var ignoreRemoveFailure = false; + var allowDowngrades = false; + var allowSameVersionUpgrades = false; + var blockUpgrades = false; + string downgradeErrorMessage = null; + string disallowUpgradeErrorMessage = null; + string removeFeatures = null; + string schedule = null; + + var upgradeCode = contextValues["UpgradeCode"]; + if (String.IsNullOrEmpty(upgradeCode)) + { + this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "UpgradeCode", node.Name.LocalName)); + } + + var productVersion = contextValues["ProductVersion"]; + if (String.IsNullOrEmpty(productVersion)) + { + this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "Version", node.Name.LocalName)); + } + + var productLanguage = contextValues["ProductLanguage"]; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AllowDowngrades": + allowDowngrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "AllowSameVersionUpgrades": + allowSameVersionUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Disallow": + blockUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "DowngradeErrorMessage": + downgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisallowUpgradeErrorMessage": + disallowUpgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MigrateFeatures": + migrateFeatures = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "IgnoreLanguage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + productLanguage = null; + } + break; + case "IgnoreRemoveFailure": + ignoreRemoveFailure = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "RemoveFeatures": + removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Schedule": + schedule = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true)); + } + + if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes")); + } + + if (allowDowngrades && allowSameVersionUpgrades) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes")); + } + + if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true)); + } + + if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes")); + } + + if (!this.Core.EncounteredError) + { + // create the row that performs the upgrade (or downgrade) + var symbol = this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + Remove = removeFeatures, + MigrateFeatures = migrateFeatures, + IgnoreRemoveFailures = ignoreRemoveFailure, + ActionProperty = WixUpgradeConstants.UpgradeDetectedProperty + }); + + if (allowDowngrades) + { + symbol.VersionMin = "0"; + symbol.Language = productLanguage; + symbol.VersionMinInclusive = true; + } + else + { + symbol.VersionMax = productVersion; + symbol.Language = productLanguage; + symbol.VersionMaxInclusive = allowSameVersionUpgrades; + } + + // Add launch condition that blocks upgrades + if (blockUpgrades) + { + this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) + { + Condition = WixUpgradeConstants.UpgradePreventedCondition, + Description = downgradeErrorMessage + }); + } + + // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted) + if (!allowDowngrades) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + VersionMin = productVersion, + Language = productLanguage, + OnlyDetect = true, + IgnoreRemoveFailures = ignoreRemoveFailure, + ActionProperty = WixUpgradeConstants.DowngradeDetectedProperty + }); + + this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) + { + Condition = WixUpgradeConstants.DowngradePreventedCondition, + Description = downgradeErrorMessage + }); + } + + // finally, schedule RemoveExistingProducts + string after = null; + switch (schedule) + { + case null: + case "afterInstallValidate": + after = "InstallValidate"; + break; + case "afterInstallInitialize": + after = "InstallInitialize"; + break; + case "afterInstallExecute": + after = "InstallExecute"; + break; + case "afterInstallExecuteAgain": + after = "InstallExecuteAgain"; + break; + case "afterInstallFinalize": + after = "InstallFinalize"; + break; + } + + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, SequenceTable.InstallExecuteSequence, "RemoveExistingProducts", afterAction: after); + } + } + + /// + /// Parses a media element. + /// + /// Element to parse. + /// Set to the PatchId if parsing Patch/Media element otherwise null. + private void ParseMediaElement(XElement node, string patchId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var id = CompilerConstants.IntegerNotSet; + string cabinet = null; + CompressionLevel? compressionLevel = null; + string diskPrompt = null; + string layout = null; + var patch = null != patchId; + string volumeLabel = null; + string source = null; + string symbols = null; + + var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Cabinet": + cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "CompressionLevel": + compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); + break; + case "DiskPrompt": + diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined + break; + case "EmbedCab": + embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Layout": + layout = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "VolumeLabel": + volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = CompilerConstants.IllegalInteger; + } + + if (YesNoType.IllegalValue != embedCab) + { + if (YesNoType.Yes == embedCab) + { + if (null == cabinet) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes")); + } + else + { + if (62 < cabinet.Length) + { + this.Core.Write(ErrorMessages.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length)); + } + + cabinet = String.Concat("#", cabinet); + } + } + else // external cabinet file + { + // external cabinet files must use 8.3 filenames + if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet)) + { + this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); + } + } + } + + if (compressionLevel.HasValue && String.IsNullOrEmpty(cabinet)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel")); + } + + if (patch) + { + // Default Source to a form of the Patch Id if none is specified. + if (null == source) + { + source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); + } + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DigitalSignature": + if (YesNoType.Yes == embedCab) + { + this.Core.Write(ErrorMessages.SignedEmbeddedCabinet(childSourceLineNumbers)); + } + else if (null == cabinet) + { + this.Core.Write(ErrorMessages.ExpectedSignedCabinetName(childSourceLineNumbers)); + } + else + { + this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + break; + case "PatchBaseline": + if (patch) + { + this.ParsePatchBaselineElement(child, id); + } + else + { + this.Core.UnexpectedElement(node, child); + } + break; + case "SymbolPath": + if (null != symbols) + { + symbols += "" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // add the row to the section + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) + { + DiskId = id, + DiskPrompt = diskPrompt, + Cabinet = cabinet, + VolumeLabel = volumeLabel, + Source = source, // the Source column is only set when creating a patch + CompressionLevel = compressionLevel, + Layout = layout + }); + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Media, id)) + { + SymbolType = SymbolPathType.Media, + SymbolId = id.ToString(CultureInfo.InvariantCulture), + SymbolPaths = symbols + }); + } + } + } + + /// + /// Parses a media template element. + /// + /// Element to parse. + /// Set to the PatchId if parsing Patch/Media element otherwise null. + private void ParseMediaTemplateElement(XElement node, string patchId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var cabinetTemplate = "cab{0}.cab"; + string diskPrompt = null; + var patch = null != patchId; + string volumeLabel = null; + int? maximumUncompressedMediaSize = null; + int? maximumCabinetSizeForLargeFileSplitting = null; + CompressionLevel? compressionLevel = null; // this defaults to 'medium' in the MSI and Burn backends + + var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "CabinetTemplate": + var authoredCabinetTemplateValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + if (!String.IsNullOrEmpty(authoredCabinetTemplateValue)) + { + cabinetTemplate = authoredCabinetTemplateValue; + } + + // Create an example cabinet name using the maximum number of cabinets supported, 999. + var exampleCabinetName = String.Format(cabinetTemplate, "###"); + if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) + { + // The example name should not match the authored template since that would nullify the + // reason for having multiple cabinets. External cabinet files must also be valid file names. + if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) + { + this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); + } + else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out. + { + this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); + } + } + break; + case "CompressionLevel": + compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); + break; + case "DiskPrompt": + diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined + this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "EmbedCab": + embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "VolumeLabel": + volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "MaximumUncompressedMediaSize": + maximumUncompressedMediaSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); + break; + case "MaximumCabinetSizeForLargeFileSplitting": + maximumCabinetSizeForLargeFileSplitting = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Compiler.MinValueOfMaxCabSizeForLargeFileSplitting, Compiler.MaxValueOfMaxCabSizeForLargeFileSplitting); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (YesNoType.Yes == embedCab) + { + cabinetTemplate = String.Concat("#", cabinetTemplate); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, 1)) + { + DiskId = 1 + }); + + this.Core.AddSymbol(new WixMediaTemplateSymbol(sourceLineNumbers) + { + CabinetTemplate = cabinetTemplate, + VolumeLabel = volumeLabel, + DiskPrompt = diskPrompt, + MaximumUncompressedMediaSize = maximumUncompressedMediaSize, + MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting, + CompressionLevel = compressionLevel + }); + + //else + //{ + // mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; + //} + + //else + //{ + // mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB) + //} + } + } + + /// + /// Parses a merge element. + /// + /// Element to parse. + /// Identifier for parent directory. + /// Disk id inherited from parent directory. + private void ParseMergeElement(XElement node, string directoryId, int diskId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var configData = String.Empty; + FileSymbolAttributes attributes = 0; + string language = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + break; + case "FileCompression": + var compress = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + attributes |= compress == YesNoType.Yes ? FileSymbolAttributes.Compressed : 0; + attributes |= compress == YesNoType.No ? FileSymbolAttributes.Uncompressed : 0; + break; + case "Language": + language = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == language) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ConfigurationData": + if (0 == configData.Length) + { + configData = this.ParseConfigurationDataElement(child); + } + else + { + configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixMergeSymbol(sourceLineNumbers, id) + { + DirectoryRef = directoryId, + SourceFile = sourceFile, + DiskId = diskId, + ConfigurationData = configData, + FileAttributes = attributes, + FeatureRef = Guid.Empty.ToString("B") + }); + + symbol.Set((int)WixMergeSymbolFields.Language, language); + } + } + + /// + /// Parses a standard directory element. + /// + /// Element to parse. + private void ParseStandardDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(id)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id)))); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty); + break; + case "Directory": + this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); + break; + case "Merge": + this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a configuration data element. + /// + /// Element to parse. + /// String in format "name=value" with '%', ',' and '=' hex encoded. + private string ParseConfigurationDataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else // need to hex encode these characters + { + name = name.Replace("%", "%25"); + name = name.Replace("=", "%3D"); + name = name.Replace(",", "%2C"); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + else // need to hex encode these characters + { + value = value.Replace("%", "%25"); + value = value.Replace("=", "%3D"); + value = value.Replace(",", "%2C"); + } + + this.Core.ParseForExtensionElements(node); + + return String.Concat(name, "=", value); + } + + /// + /// Parses a Level element. + /// + /// Element to parse. + /// Id of the parent Feature element. + private void ParseLevelElement(XElement node, string featureId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + int? level = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!level.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); + } + + if (String.IsNullOrEmpty(condition)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (CompilerConstants.IntegerNotSet == level) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); + level = CompilerConstants.IllegalInteger; + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ConditionSymbol(sourceLineNumbers) + { + FeatureRef = featureId, + Level = level.Value, + Condition = condition + }); + } + } + } + + /// + /// Parses a merge reference element. + /// + /// Element to parse. + /// Parents complex reference type. + /// Identifier for parent feature or feature group. + private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixMerge, id); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary)); + } + + /// + /// Parses a mime element. + /// + /// Element to parse. + /// Identifier for parent extension. + /// Identifier for parent component. + /// Flag if the parent element is advertised. + /// Content type if this is the default for the MIME type. + private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string classId = null; + string contentType = null; + var advertise = parentAdvertised; + var returnContentType = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Advertise": + advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Class": + classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "ContentType": + contentType = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Default": + returnContentType = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == contentType) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType")); + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + this.Core.ParseForExtensionElements(node); + + if (YesNoType.Yes == advertise) + { + if (YesNoType.Yes != parentAdvertised) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString())); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MIMESymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, contentType)) + { + ContentType = contentType, + ExtensionRef = extension, + CLSID = classId + }); + } + } + else if (YesNoType.No == advertise) + { + if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised) + { + this.Core.Write(ErrorMessages.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers)); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId); + if (null != classId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId); + } + } + + return YesNoType.Yes == returnContentType ? contentType : null; + } + + /// + /// Parses a patch property element. + /// + /// The element to parse. + /// True if parsing an patch element. + private void ParsePatchPropertyElement(XElement node, bool patch) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string company = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Company": + company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (patch) + { + // /Patch/PatchProperty goes directly into MsiPatchMetadata table + this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, company, name)) + { + Company = company, + Property = name, + Value = value + }); + } + else + { + if (null != company) + { + this.Core.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); + } + this.AddPrivateProperty(sourceLineNumbers, name, value); + } + } + + /// + /// Adds a row to the properties table. + /// + /// Source line numbers. + /// Name of the property. + /// Value of the property. + private void AddPrivateProperty(SourceLineNumber sourceLineNumbers, string name, string value) + { + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) + { + Value = value + }); + } + } + + /// + /// Parses a TargetProductCode element. + /// + /// The element to parse. + /// The id from the node. + private string ParseTargetProductCodeElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (id.Length > 0 && "*" != id) + { + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; + } + + /// + /// Parses a ReplacePatch element. + /// + /// The element to parse. + /// The id from the node. + private string ParseReplacePatchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; + } + + /// + /// Parses a symbol path element. + /// + /// The element to parse. + /// The path from the node. + private string ParseSymbolPathElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string path = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Path": + path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == path) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path")); + } + + this.Core.ParseForExtensionElements(node); + + return path; + } + + /// + /// Parses the All element under a PatchFamily. + /// + /// The element to parse. + private void ParseAllElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + // find unexpected attributes + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + this.Core.UnexpectedAttribute(node, attrib); + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + // Always warn when using the All element. + this.Core.Write(WarningMessages.AllChangesIncludedInPatch(sourceLineNumbers)); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) + { + Table = "*", + PrimaryKeys = "*", + }); + } + } + + /// + /// Parses all reference elements under a PatchFamily. + /// + /// The element to parse. + /// Table that reference was made to. + private void ParsePatchChildRefElement(XElement node, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) + { + Table = tableName, + PrimaryKeys = id + }); + } + } + + /// + /// Parses a PatchBaseline element. + /// + /// The element to parse. + /// Media index from parent element. + private void ParsePatchBaselineElement(XElement node, int? diskId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var parsedValidate = false; + var validationFlags = TransformFlags.PatchTransformDefault; + string baselineFile = null; + string updateFile = null; + string transformFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "BaselineFile": + baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpdateFile": + updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TransformFile": + transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (27 < id.Id.Length) + { + this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); + } + + if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) + { + if (String.IsNullOrEmpty(baselineFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); + } + + if (String.IsNullOrEmpty(updateFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); + } + + if (!String.IsNullOrEmpty(transformFile)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); + } + } + else if (String.IsNullOrEmpty(transformFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Validate": + if (parsedValidate) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + } + else + { + this.ParseValidateElement(child, ref validationFlags); + parsedValidate = true; + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchBaselineSymbol(sourceLineNumbers, id) + { + DiskId = diskId ?? 1, + ValidationFlags = validationFlags, + BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, + UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, + TransformFile = new IntermediateFieldPathValue { Path = transformFile }, + }); + } + } + + /// + /// Parses a Validate element. + /// + /// The element to parse. + /// TransformValidation flags to use when creating the authoring patch transform. + private void ParseValidateElement(XElement node, ref TransformFlags validationFlags) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ProductId": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ValidateProduct; + } + else + { + validationFlags &= ~TransformFlags.ValidateProduct; + } + break; + case "ProductLanguage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ValidateLanguage; + } + else + { + validationFlags &= ~TransformFlags.ValidateLanguage; + } + break; + case "ProductVersion": + var check = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + validationFlags &= ~TransformFlags.ProductVersionMask; + switch (check) + { + case "Major": + case "major": + validationFlags |= TransformFlags.ValidateMajorVersion; + break; + case "Minor": + case "minor": + validationFlags |= TransformFlags.ValidateMinorVersion; + break; + case "Update": + case "update": + validationFlags |= TransformFlags.ValidateUpdateVersion; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update")); + break; + } + break; + case "ProductVersionOperator": + var op = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + validationFlags &= ~TransformFlags.ProductVersionOperatorMask; + switch (op) + { + case "Lesser": + case "lesser": + validationFlags |= TransformFlags.ValidateNewLessBaseVersion; + break; + case "LesserOrEqual": + case "lesserOrEqual": + validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion; + break; + case "Equal": + case "equal": + validationFlags |= TransformFlags.ValidateNewEqualBaseVersion; + break; + case "GreaterOrEqual": + case "greaterOrEqual": + validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion; + break; + case "Greater": + case "greater": + validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater")); + break; + } + break; + case "UpgradeCode": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ValidateUpgradeCode; + } + else + { + validationFlags &= ~TransformFlags.ValidateUpgradeCode; + } + break; + case "IgnoreAddExistingRow": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorAddExistingRow; + } + else + { + validationFlags &= ~TransformFlags.ErrorAddExistingRow; + } + break; + case "IgnoreAddExistingTable": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorAddExistingTable; + } + else + { + validationFlags &= ~TransformFlags.ErrorAddExistingTable; + } + break; + case "IgnoreDeleteMissingRow": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorDeleteMissingRow; + } + else + { + validationFlags &= ~TransformFlags.ErrorDeleteMissingRow; + } + break; + case "IgnoreDeleteMissingTable": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorDeleteMissingTable; + } + else + { + validationFlags &= ~TransformFlags.ErrorDeleteMissingTable; + } + break; + case "IgnoreUpdateMissingRow": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorUpdateMissingRow; + } + else + { + validationFlags &= ~TransformFlags.ErrorUpdateMissingRow; + } + break; + case "IgnoreChangingCodePage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorChangeCodePage; + } + else + { + validationFlags &= ~TransformFlags.ErrorChangeCodePage; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + } + + private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename) + { + if (!String.IsNullOrEmpty(subdirectory)) + { + if (String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName)); + } + else + { + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); + } + } + + return directoryId; + } + } +} diff --git a/src/wix/WixToolset.Core/CompilerCore.cs b/src/wix/WixToolset.Core/CompilerCore.cs new file mode 100644 index 00000000..727084eb --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerCore.cs @@ -0,0 +1,1166 @@ +// 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.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Reflection; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal enum ValueListKind + { + /// + /// A list of values with nothing before the final value. + /// + None, + + /// + /// A list of values with 'and' before the final value. + /// + And, + + /// + /// A list of values with 'or' before the final value. + /// + Or + } + + /// + /// Core class for the compiler. + /// + internal class CompilerCore + { + internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; + internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; + + // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) + private static readonly List BuiltinBundleVariables = new List( + new string[] { + "AdminToolsFolder", + "AppDataFolder", + "CommonAppDataFolder", + "CommonFiles64Folder", + "CommonFilesFolder", + "CompatibilityMode", + "Date", + "DesktopFolder", + "FavoritesFolder", + "FontsFolder", + "InstallerName", + "InstallerVersion", + "LocalAppDataFolder", + "LogonUser", + "MyPicturesFolder", + "NTProductType", + "NTSuiteBackOffice", + "NTSuiteDataCenter", + "NTSuiteEnterprise", + "NTSuitePersonal", + "NTSuiteSmallBusiness", + "NTSuiteSmallBusinessRestricted", + "NTSuiteWebServer", + "PersonalFolder", + "Privileged", + "ProgramFiles64Folder", + "ProgramFiles6432Folder", + "ProgramFilesFolder", + "ProgramMenuFolder", + "RebootPending", + "SendToFolder", + "ServicePackLevel", + "StartMenuFolder", + "StartupFolder", + "System64Folder", + "SystemFolder", + "TempFolder", + "TemplateFolder", + "TerminalServer", + "UserLanguageID", + "UserUILanguageID", + "VersionMsi", + "VersionNT", + "VersionNT64", + "WindowsFolder", + "WindowsVolume", + "WixBundleAction", + "WixBundleForcedRestartPackage", + "WixBundleElevated", + "WixBundleInstalled", + "WixBundleProviderKey", + "WixBundleTag", + "WixBundleVersion", + }); + + private static readonly List DisallowedMsiProperties = new List( + new string[] { + "ACTION", + "ADDLOCAL", + "ADDSOURCE", + "ADDDEFAULT", + "ADVERTISE", + "ALLUSERS", + "REBOOT", + "REINSTALL", + "REINSTALLMODE", + "REMOVE" + }); + + private readonly Dictionary extensions; + private readonly IParseHelper parseHelper; + private readonly Intermediate intermediate; + private readonly IMessaging messaging; + private Dictionary activeSectionCachedInlinedDirectoryIds; + private HashSet activeSectionSimpleReferences; + + /// + /// Constructor for all compiler core. + /// + /// The Intermediate object representing compiled source document. + /// + /// + /// The WiX extensions collection. + internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary extensions) + { + this.extensions = extensions; + this.parseHelper = parseHelper; + this.intermediate = intermediate; + this.messaging = messaging; + } + + /// + /// Gets the section the compiler is currently emitting symbols into. + /// + /// The section the compiler is currently emitting symbols into. + public IntermediateSection ActiveSection { get; private set; } + + /// + /// Gets whether the compiler core encountered an error while processing. + /// + /// Flag if core encountered an error during processing. + public bool EncounteredError => this.messaging.EncounteredError; + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages { get; set; } + + /// + /// Add a symbol to the active section. + /// + /// Symbol to add. + public T AddSymbol(T symbol) + where T : IntermediateSymbol + { + return this.ActiveSection.AddSymbol(symbol); + } + + /// + /// Convert a bit array into an int value. + /// + /// The bit array to convert. + /// The converted int value. + public int CreateIntegerFromBitArray(BitArray bits) + { + if (32 != bits.Length) + { + throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits"); + } + + int[] intArray = new int[1]; + bits.CopyTo(intArray, 0); + + return intArray[0]; + } + + /// + /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array. + /// + /// Array of attributes that map to bits. + /// Name of attribute to check. + /// Value of attribute to check. + /// The bit array in which the bit will be set if found. + /// The offset into the bit array. + /// true if the bit was set; false otherwise. + public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset) + { + for (int i = 0; i < attributeNames.Length; i++) + { + if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal)) + { + bits.Set(i + offset, YesNoType.Yes == attributeValue); + return true; + } + } + + return false; + } + + internal void InnerTextDisallowed(XElement element) + { + this.parseHelper.InnerTextDisallowed(element); + } + + /// + /// Verifies that a filename is ambiguous. + /// + /// Filename to verify. + /// true if the filename is ambiguous; false otherwise. + public static bool IsAmbiguousFilename(string filename) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + + var tilde = filename.IndexOf('~'); + return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); + } + + /// + /// Verifies that a value is a legal identifier. + /// + /// The value to verify. + /// true if the value is an identifier; false otherwise. + public bool IsValidIdentifier(string value) + { + return this.parseHelper.IsValidIdentifier(value); + } + + /// + /// Verifies if an identifier is a valid loc identifier. + /// + /// Identifier to verify. + /// True if the identifier is a valid loc identifier. + public bool IsValidLocIdentifier(string identifier) + { + return this.parseHelper.IsValidLocIdentifier(identifier); + } + + /// + /// Verifies if a filename is a valid long filename. + /// + /// Filename to verify. + /// true if wildcards are allowed in the filename. + /// true if relative paths are allowed in the filename. + /// True if the filename is a valid long filename + public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false) + { + return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); + } + + /// + /// Verifies if a filename is a valid short filename. + /// + /// Filename to verify. + /// true if wildcards are allowed in the filename. + /// True if the filename is a valid short filename + public bool IsValidShortFilename(string filename, bool allowWildcards) + { + return this.parseHelper.IsValidShortFilename(filename, allowWildcards); + } + + /// + /// Replaces the illegal filename characters to create a legal name. + /// + /// Filename to make valid. + /// Replacement string for invalid characters in filename. + /// Valid filename. + public static string MakeValidLongFileName(string filename, char replace) + { + if (String.IsNullOrEmpty(filename)) + { + return filename; + } + + StringBuilder sb = null; + + var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); + while (found != -1) + { + if (sb == null) + { + sb = new StringBuilder(filename); + } + + sb[found] = replace; + + found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; + } + + return sb?.ToString() ?? filename; + } + + /// + /// Verifies the given string is a valid product version. + /// + /// The product version to verify. + /// True if version is a valid product version + public static bool IsValidProductVersion(string version) + { + if (!Common.IsValidBinderVariable(version)) + { + Version ver = new Version(version); + + if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build) + { + return false; + } + } + + return true; + } + + /// + /// Verifies the given string is a valid module or bundle version. + /// + /// The version to verify. + /// True if version is a valid module or bundle version. + public static bool IsValidModuleOrBundleVersion(string version) + { + return Common.IsValidFourPartVersion(version); + } + + /// + /// Creates group and ordering information. + /// + /// Source line numbers. + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of this item. + /// Identifier for this item. + /// Type of previous item, if known. + /// Identifier of previous item, if known + public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, + ComplexReferenceParentType parentType, string parentId, + ComplexReferenceChildType type, string id, + ComplexReferenceChildType previousType, string previousId) + { + if (this.EncounteredError) + { + return; + } + + if (parentType != ComplexReferenceParentType.Unknown && parentId != null) + { + this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); + } + + if (previousType != ComplexReferenceChildType.Unknown && previousId != null) + { + // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? + // TODO: Also, we could potentially include an 'Attributes' field to track things like + // 'before' vs. 'after', and explicit vs. inferred dependencies. + this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) + { + ItemType = type, + ItemIdRef = id, + DependsOnType = previousType, + DependsOnIdRef = previousId, + }); + } + } + + /// + /// Creates a version 3 name-based UUID. + /// + /// The namespace UUID. + /// The value. + /// The generated GUID for the given namespace and value. + public string CreateGuid(Guid namespaceGuid, string value) + { + return this.parseHelper.CreateGuid(namespaceGuid, value); + } + + /// + /// Creates directories using the inline directory syntax. + /// + /// Source line information. + /// Optional identifier of parent directory. + /// Optional inline syntax to override attribute's value. + /// Identifier of the leaf directory created. + public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) + { + return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); + } + + /// + /// Creates a Registry row in the active section. + /// + /// Source and line number of the current row. + /// The registry entry root. + /// The registry entry key. + /// The registry entry name. + /// The registry entry value. + /// The component which will control installation/uninstallation of the registry entry. + public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId) + { + return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true); + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol name of the simple reference. + /// The primary key of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) + { + if (!this.EncounteredError) + { + var id = String.Concat(symbolName, ":", primaryKey); + + // If this simple reference hasn't been added to the active section already, add it. + if (this.activeSectionSimpleReferences.Add(id)) + { + this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey); + } + } + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol name of the simple reference. + /// The primary keys of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) + { + if (!this.EncounteredError) + { + var joinedKeys = String.Join("/", primaryKeys); + var id = String.Concat(symbolName, ":", joinedKeys); + + // If this simple reference hasn't been added to the active section already, add it. + if (this.activeSectionSimpleReferences.Add(id)) + { + this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys); + } + } + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol definition of the simple reference. + /// The primary key of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) + { + this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey); + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol definition of the simple reference. + /// The primary keys of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) + { + this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys); + } + + /// + /// A row in the WixGroup table is added for this child node and its parent node. + /// + /// Source line information for the row. + /// Type of child's complex reference parent. + /// Id of the parenet node. + /// Complex reference type of child + /// Id of the Child Node. + public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) + { + if (!this.EncounteredError) + { + this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId); + } + } + + /// + /// Add the appropriate symbols to make sure that the given table shows up + /// in the resulting output. + /// + /// Source line numbers. + /// Name of the table to ensure existance of. + public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName) + { + if (!this.EncounteredError) + { + this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName); + } + } + + /// + /// Add the appropriate symbols to make sure that the given table shows up + /// in the resulting output. + /// + /// Source line numbers. + /// Definition of the table to ensure existance of. + public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + { + if (!this.EncounteredError) + { + this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition); + } + } + + /// + /// Get an attribute value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. + /// The attribute's value. + public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) + { + return this.parseHelper.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); + } + + /// + /// Get a valid code page by web name or number from a string attribute. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// A valid code page integer value. + public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + if (null == attribute) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + try + { + return Common.GetValidCodePage(value); + } + catch (NotSupportedException) + { + this.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + } + + return CompilerConstants.IllegalInteger; + } + + /// + /// Get a valid code page by web name or number from a string attribute. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Whether to allow Unicode (UCS) or UTF code pages. + /// A valid code page integer value or variable expression. + public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false) + { + if (null == attribute) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + // Allow for localization of code page names and values. + if (this.IsValidLocIdentifier(value)) + { + return value; + } + + try + { + var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers); + return codePage.ToString(CultureInfo.InvariantCulture); + } + catch (NotSupportedException) + { + // Not a valid windows code page. + this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + } + catch (WixException e) + { + this.messaging.Write(e.Error); + } + + return null; + } + + /// + /// Get an integer attribute value and displays an error for an illegal integer value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's integer value or a special value if an error occurred during conversion. + public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + return this.parseHelper.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum); + } + + /// + /// Get a long integral attribute value and displays an error for an illegal long value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's long value or a special value if an error occurred during conversion. + public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) + { + return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum); + } + + /// + /// Get a date time attribute value and display errors for illegal values. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Int representation of the date time. + public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + string value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + try + { + DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat); + + return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) + + (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2); + } + catch (ArgumentOutOfRangeException) + { + this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (FormatException) + { + this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (OverflowException) + { + this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return CompilerConstants.IllegalInteger; + } + + /// + /// Get an integer attribute value or localize variable and displays an error for + /// an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion. + public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value)) + { + return value; + } + else + { + try + { + var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); + + if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) + { + this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); + } + else if (minimum > integer || maximum < integer) + { + this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); + integer = CompilerConstants.IllegalInteger; + } + + return value; + } + catch (FormatException) + { + this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (OverflowException) + { + this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + } + + return null; + } + + /// + /// Get a guid attribute value and displays an error for an illegal guid value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Determines whether the guid can be automatically generated. + /// If true, no error is raised on empty value. If false, an error is raised. + /// The attribute's guid value or a special value if an error occurred. + public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) + { + return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty); + } + + /// + /// Get an identifier attribute value and displays an error for an illegal identifier value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's identifier value or a special value if an error occurred. + public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute); + } + + /// + /// Get an identifier attribute value and displays an error for an illegal identifier value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's identifier value or a special value if an error occurred. + public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a yes/no value and displays an error for an illegal yes/no value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's YesNoType value. + public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a yes/no/default value and displays an error for an illegal yes/no value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's YesNoDefaultType value. + public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a short filename value and displays an error for an illegal short filename value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// true if wildcards are allowed in the filename. + /// The attribute's short filename value. + public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value)) + { + this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else if (CompilerCore.IsAmbiguousFilename(value)) + { + this.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return value; + } + + /// + /// Gets a long filename value and displays an error for an illegal long filename value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// true if wildcards are allowed in the filename. + /// true if relative paths are allowed in the filename. + /// The attribute's long filename value. + public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false) + { + return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative); + } + + /// + /// Gets a version value or possibly a binder variable and displays an error for an illegal version value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's version value. + public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Whether HKMU is returned as -1 (true), or treated as an error (false). + /// The attribute's RegisitryRootType value. + public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) + { + return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu); + } + + /// + /// Gets a Bundle variable value and displays an error for an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's value. + public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + string value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (!String.IsNullOrEmpty(value)) + { + if (CompilerCore.BuiltinBundleVariables.Contains(value)) + { + string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables); + this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues)); + } + } + + return value; + } + + /// + /// Gets an MsiProperty name value and displays an error for an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's value. + public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + string value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + if (CompilerCore.DisallowedMsiProperties.Contains(value)) + { + string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties); + this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues)); + } + } + + return value; + } + + /// + /// Checks if the string contains a property (i.e. "foo[Property]bar") + /// + /// String to evaluate for properties. + /// True if a property is found in the string. + public bool ContainsProperty(string possibleProperty) + { + return this.parseHelper.ContainsProperty(possibleProperty); + } + + /// + /// Generate an identifier by hashing data from the row. + /// + /// Three letter or less prefix for generated row identifier. + /// Information to hash. + /// The generated identifier. + public Identifier CreateIdentifier(string prefix, params string[] args) + { + return this.parseHelper.CreateIdentifier(prefix, args); + } + + /// + /// Create an identifier based on passed file name + /// + /// File name to generate identifer from + /// + public Identifier CreateIdentifierFromFilename(string filename) + { + return this.parseHelper.CreateIdentifierFromFilename(filename); + } + + /// + /// Attempts to use an extension to parse the attribute. + /// + /// Element containing attribute to be parsed. + /// Attribute to be parsed. + /// Extra information about the context in which this element is being parsed. + public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary context = null) + { + this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context); + } + + /// + /// Attempts to use an extension to parse the element. + /// + /// Element containing element to be parsed. + /// Element to be parsed. + /// Extra information about the context in which this element is being parsed. + public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary context = null) + { + this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); + } + + /// + /// Process all children of the element looking for extensions and erroring on the unexpected. + /// + /// Element to parse children. + public void ParseForExtensionElements(XElement element) + { + this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element); + } + + /// + /// Attempts to use an extension to parse the element, with support for setting component keypath. + /// + /// Element containing element to be parsed. + /// Element to be parsed. + /// Extra information about the context in which this element is being parsed. + public IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary context) + { + return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); + } + + /// + /// Displays an unexpected attribute error if the attribute is not the namespace attribute. + /// + /// Element containing unexpected attribute. + /// The unexpected attribute. + public void UnexpectedAttribute(XElement element, XAttribute attribute) + { + this.parseHelper.UnexpectedAttribute(element, attribute); + } + + /// + /// Display an unexepected element error. + /// + /// The parent element. + /// The unexpected child element. + public void UnexpectedElement(XElement parentElement, XElement childElement) + { + this.parseHelper.UnexpectedElement(parentElement, childElement); + } + + /// + /// Sends a message. + /// + /// Message to write. + public void Write(Message message) + { + this.messaging.Write(message); + } + + /// + /// Verifies that the calling assembly version is equal to or newer than the given . + /// + /// Source line information about the owner element. + /// The version required of the calling assembly. + internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion) + { + // an null or empty string means any version will work + if (!String.IsNullOrEmpty(requiredVersion)) + { + Assembly caller = Assembly.GetCallingAssembly(); + AssemblyName name = caller.GetName(); + FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location); + + Version versionRequired = new Version(requiredVersion); + Version versionCurrent = new Version(fv.FileVersion); + + if (versionRequired > versionCurrent) + { + if (this.GetType().Assembly.Equals(caller)) + { + this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired)); + } + else + { + this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name)); + } + } + } + } + + /// + /// Creates a new section and makes it the active section in the core. + /// + /// Unique identifier for the section. + /// Type of section to create. + /// Unique identifier for the compilation. + /// New section. + internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) + { + this.ActiveSection = this.CreateSection(id, type, compilationId); + + this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); + this.activeSectionSimpleReferences = new HashSet(); + + return this.ActiveSection; + } + + /// + /// Creates a new section. + /// + /// Unique identifier for the section. + /// Type of section to create. + /// Unique identifier for the compilation. + /// New section. + internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) + { + var section = new IntermediateSection(id, type, compilationId); + + this.intermediate.AddSection(section); + + return section; + } + + /// + /// Creates WixComplexReference and WixGroup rows in the active section. + /// + /// Source line information. + /// The parent type. + /// The parent id. + /// The parent language. + /// The child type. + /// The child id. + /// Whether the child is primary. + public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) + { + this.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary); + } + + /// + /// Creates a directory row from a name. + /// + /// Source line information. + /// Optional identifier for the new row. + /// Optional identifier for the parent row. + /// Long name of the directory. + /// Optional short name of the directory. + /// Optional source name for the directory. + /// Optional short source name for the directory. + /// Identifier for the newly created row. + internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) + { + return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); + } + + public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) + { + this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); + } + + internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) + { + return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); + } + + private static string CreateValueList(ValueListKind kind, IEnumerable values) + { + // Ideally, we could denote the list kind (and the list itself) directly in the + // message XML, and detect and expand in the MessageHandler.GenerateMessageString() + // method. Doing so would make vararg-style messages much easier, but impacts + // every single message we format. For now, callers just have to know when a + // message takes a list of values in a single string argument, the caller will + // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge + // that the list is an 'and' or 'or' list.) + + // For a localizable solution, we need to be able to get the list format string + // from resources. We aren't currently localized right now, so the values are + // just hard-coded. + const string valueFormat = "'{0}'"; + const string valueSeparator = ", "; + string terminalTerm = String.Empty; + + switch (kind) + { + case ValueListKind.None: + terminalTerm = ""; + break; + case ValueListKind.And: + terminalTerm = "and "; + break; + case ValueListKind.Or: + terminalTerm = "or "; + break; + } + + StringBuilder list = new StringBuilder(); + + // This weird construction helps us determine when we're adding the last value + // to the list. Instead of adding them as we encounter them, we cache the current + // value and append the *previous* one. + string previousValue = null; + bool haveValues = false; + foreach (string value in values) + { + if (null != previousValue) + { + if (haveValues) + { + list.Append(valueSeparator); + } + list.AppendFormat(valueFormat, previousValue); + haveValues = true; + } + + previousValue = value; + } + + // If we have no previous value, that means that the list contained no values, and + // something has gone very wrong. + Debug.Assert(null != previousValue); + if (null != previousValue) + { + if (haveValues) + { + list.Append(valueSeparator); + list.Append(terminalTerm); + } + list.AppendFormat(valueFormat, previousValue); + haveValues = true; + } + + return list.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core/CompilerErrors.cs b/src/wix/WixToolset.Core/CompilerErrors.cs new file mode 100644 index 00000000..10646dfd --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerErrors.cs @@ -0,0 +1,43 @@ +// 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; + + internal static class CompilerErrors + { + public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars) + { + return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars); + } + + public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue) + { + return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue); + } + + public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) + { + return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); + } + + public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) + { + return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + IllegalCharactersInProvider = 5400, + ReservedValue = 5401, + + IllegalName = 6601, + ExampleRegid = 6602, + } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. + } +} diff --git a/src/wix/WixToolset.Core/CompilerWarnings.cs b/src/wix/WixToolset.Core/CompilerWarnings.cs new file mode 100644 index 00000000..5c11b878 --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerWarnings.cs @@ -0,0 +1,65 @@ +// 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; + + internal static class CompilerWarnings + { + public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) + { + return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId); + } + + public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) + { + return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId); + } + + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); + } + + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id); + } + + public static Message PropertyRemoved(string name) + { + return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name); + } + + public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id); + } + + public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id); + } + + public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId) + { + return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + ProvidesKeyNotFound = 5431, + RequiresKeyNotFound = 5432, + PropertyRemoved = 5433, + DiscouragedVersionAttribute = 5434, + Win64Component = 5435, + DirectoryRefStandardDirectoryDeprecated = 5436, + DefiningStandardDirectoryDeprecated = 5437, + } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Bundle.cs b/src/wix/WixToolset.Core/Compiler_Bundle.cs new file mode 100644 index 00000000..6d2e75f7 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Bundle.cs @@ -0,0 +1,3266 @@ +// 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.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName); + private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName); + private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName); + + /// + /// Parses an ApprovedExeForElevation element. + /// + /// Element to parse + private void ParseApprovedExeForElevation(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string valueName = null; + var win64 = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + var attributes = WixApprovedExeForElevationAttributes.None; + + if (win64) + { + attributes |= WixApprovedExeForElevationAttributes.Win64; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id) + { + Key = key, + ValueName = valueName, + Attributes = attributes, + }); + } + } + + /// + /// Parses a Bundle element. + /// + /// Element to parse + private void ParseBundleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string copyright = null; + string aboutUrl = null; + var compressed = YesNoDefaultType.Default; + WixBundleAttributes attributes = 0; + string helpTelephone = null; + string helpUrl = null; + string manufacturer = null; + string name = null; + string tag = null; + string updateUrl = null; + string upgradeCode = null; + string version = null; + string condition = null; + string parentName = null; + + string fileSystemSafeBundleName = null; + string logVariablePrefixAndExtension = null; + string iconSourceFile = null; + string splashScreenSourceFile = null; + + // Process only standard attributes until the active section is initialized. + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AboutUrl": + aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Compressed": + compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Copyright": + copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisableModify": + var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (value) + { + case "button": + attributes |= WixBundleAttributes.SingleChangeUninstallButton; + break; + case "yes": + attributes |= WixBundleAttributes.DisableModify; + break; + case "no": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); + break; + } + break; + case "DisableRemove": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixBundleAttributes.DisableRemove; + } + break; + case "HelpTelephone": + helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HelpUrl": + helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "IconSourceFile": + iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ParentName": + parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ProviderKey": + // This can't be processed until we create the section. + break; + case "SplashScreenSourceFile": + splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Tag": + tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpdateUrl": + updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (String.IsNullOrEmpty(version)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) + { + this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); + } + + if (String.IsNullOrEmpty(upgradeCode)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); + } + + if (String.IsNullOrEmpty(copyright)) + { + if (String.IsNullOrEmpty(manufacturer)) + { + copyright = "Copyright (c). All rights reserved."; + } + else + { + copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); + } + } + + if (String.IsNullOrEmpty(name)) + { + logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log"); + } + else + { + // Ensure only allowable path characters are in "name" (and change spaces to underscores). + fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); + logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); + } + + this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; + this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId); + + // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ProviderKey": + this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); + break; + // Unknown attributes were reported earlier. + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var baSeen = false; + var chainSeen = false; + var logSeen = false; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ApprovedExeForElevation": + this.ParseApprovedExeForElevation(child); + break; + case "BootstrapperApplication": + if (baSeen) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); + } + this.ParseBootstrapperApplicationElement(child); + baSeen = true; + break; + case "BootstrapperApplicationRef": + this.ParseBootstrapperApplicationRefElement(child); + break; + case "BundleCustomData": + this.ParseBundleCustomDataElement(child); + break; + case "BundleCustomDataRef": + this.ParseBundleCustomDataRefElement(child); + break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); + break; + case "OptionalUpdateRegistration": + this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); + break; + case "Chain": + if (chainSeen) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); + } + this.ParseChainElement(child); + chainSeen = true; + break; + case "Container": + this.ParseContainerElement(child); + break; + case "ContainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer); + break; + case "Log": + if (logSeen) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); + } + logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); + logSeen = true; + break; + case "PayloadGroup": + this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads); + break; + case "PayloadGroupRef": + this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null); + break; + case "RelatedBundle": + this.ParseRelatedBundleElement(child); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetVariable": + this.ParseSetVariableElement(child); + break; + case "SetVariableRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); + break; + case "SoftwareTag": + this.ParseBundleTagElement(child); + break; + case "Update": + this.ParseUpdateElement(child); + break; + case "Variable": + this.ParseVariableElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!chainSeen) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + Version = version, + Copyright = copyright, + Name = name, + Manufacturer = manufacturer, + Attributes = attributes, + AboutUrl = aboutUrl, + HelpUrl = helpUrl, + HelpTelephone = helpTelephone, + UpdateUrl = updateUrl, + Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, + IconSourceFile = iconSourceFile, + SplashScreenSourceFile = splashScreenSourceFile, + Condition = condition, + Tag = tag, + Platform = this.CurrentPlatform, + ParentName = parentName, + }); + + if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) + { + var split = logVariablePrefixAndExtension.Split(':'); + symbol.LogPathVariable = split[0]; + symbol.LogPrefix = split[1]; + symbol.LogExtension = split[2]; + } + + if (null != upgradeCode) + { + this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) + { + BundleId = upgradeCode, + Action = RelatedBundleActionType.Upgrade, + }); + } + + this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) + { + Name = "bundle-attached.cab", + Type = ContainerType.Attached, + }); + + // Ensure that the bundle stores the well-known persisted values. + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME)) + { + Hidden = false, + Persisted = true, + }); + + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) + { + Hidden = false, + Persisted = true, + }); + + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) + { + Hidden = false, + Persisted = true, + }); + + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) + { + Hidden = false, + Persisted = true, + }); + } + } + + /// + /// Parse a Container element. + /// + /// Element to parse + /// + private string ParseLogElement(XElement node, string fileSystemSafeBundleName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var disableLog = YesNoType.NotSet; + var variable = "WixBundleLog"; + var logPrefix = fileSystemSafeBundleName ?? "Setup"; + var logExtension = ".log"; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Disable": + disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "PathVariable": + variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Prefix": + logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Extension": + logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!logExtension.StartsWith(".", StringComparison.Ordinal)) + { + logExtension = String.Concat(".", logExtension); + } + + this.Core.ParseForExtensionElements(node); + + return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); + } + + /// + /// Parse a Container element. + /// + /// Element to parse + private void ParseContainerElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string downloadUrl = null; + string name = null; + var type = ContainerType.Detached; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + if (id?.Id == BurnConstants.BurnUXContainerName || id?.Id == BurnConstants.BurnDefaultAttachedContainerName) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + break; + case "DownloadUrl": + downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Type": + var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeString) + { + case "attached": + type = ContainerType.Attached; + break; + case "detached": + type = ContainerType.Detached; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(name)) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + else if (null == name) + { + name = id.Id; + } + + if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PackageGroupRef": + this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id) + { + Name = name, + Type = type, + DownloadUrl = downloadUrl + }); + } + } + + /// + /// Parse the BoostrapperApplication element. + /// + /// Element to parse + private void ParseBootstrapperApplicationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "BootstrapperApplicationDll": + previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (id != null) + { + this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parse the BoostrapperApplication element. + /// + /// Element to parse + /// + /// + /// + private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + Id = defaultId, + }; + var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DpiAwareness": + var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (dpiAwarenessValue) + { + case "gdiScaled": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled; + break; + case "perMonitor": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor; + break; + case "perMonitorV2": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; + break; + case "system": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System; + break; + case "unaware": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the Id is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); + this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) + { + Name = "bundle-ux.cab", + Type = ContainerType.Attached + }); + + this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) + { + DpiAwareness = dpiAwareness, + }); + } + + return compilerPayload.Id; + } + + /// + /// Parse the BoostrapperApplicationRef element. + /// + /// Element to parse + private void ParseBootstrapperApplicationRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (String.IsNullOrEmpty(id)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id); + } + } + + + + /// + /// Parses a BundleCustomData element. + /// + /// Element to parse. + private void ParseBundleCustomDataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string customDataId = null; + WixBundleCustomDataType? customDataType = null; + string extensionId = null; + var attributeDefinitions = new List(); + var foundAttributeDefinitions = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "bootstrapperApplication": + customDataType = WixBundleCustomDataType.BootstrapperApplication; + break; + case "bundleExtension": + customDataType = WixBundleCustomDataType.BundleExtension; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); + customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. + break; + } + break; + case "ExtensionId": + extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == customDataId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var hasExtensionId = null != extensionId; + if (!customDataType.HasValue) + { + customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; + } + + if (!customDataType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + else if (hasExtensionId) + { + if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); + } + } + else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleAttributeDefinition": + foundAttributeDefinitions = true; + + var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); + if (attributeDefinition != null) + { + attributeDefinitions.Add(attributeDefinition); + } + break; + case "BundleElement": + this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (attributeDefinitions.Count > 0) + { + if (!this.Core.EncounteredError) + { + var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); + + this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId)) + { + AttributeNames = attributeNames, + Type = customDataType.Value, + BundleExtensionRef = extensionId, + }); + } + } + else if (!foundAttributeDefinitions) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); + } + } + + /// + /// Parses a BundleCustomDataRef element. + /// + /// Element to parse. + private void ParseBundleCustomDataRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string customDataId = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == customDataId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleElement": + this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a BundleAttributeDefinition element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// BundleCustomData Id. + private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) + { + string attributeName = null; + + foreach (var attrib in node.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Id": + attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + + if (null == attributeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (this.Core.EncounteredError) + { + return null; + } + + var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName)) + { + CustomDataRef = customDataId, + Name = attributeName, + }); + return customDataAttribute; + } + + /// + /// Parses a BundleElement element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// BundleCustomData Id. + private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) + { + var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleAttribute": + string attributeName = null; + string value = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Id": + attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + if (null == attributeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName)) + { + ElementId = elementId, + AttributeRef = attributeName, + CustomDataRef = customDataId, + Value = value, + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); + } + } + + /// + /// Parse the BundleExtension element. + /// + /// Element to parse + private void ParseBundleExtensionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the Id is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); + previousId = compilerPayload.Id; + previousType = ComplexReferenceChildType.Payload; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // Add the BundleExtension. + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) + { + PayloadRef = compilerPayload.Id.Id, + }); + } + } + + /// + /// Parse the OptionalUpdateRegistration element. + /// + /// The element to parse. + /// The manufacturer. + /// The product family. + /// The bundle name. + private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) + { + const string defaultClassification = "Update"; + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string manufacturer = null; + string department = null; + string productFamily = null; + string name = null; + var classification = defaultClassification; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Department": + department = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ProductFamily": + productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Classification": + classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(manufacturer)) + { + if (!String.IsNullOrEmpty(defaultManufacturer)) + { + manufacturer = defaultManufacturer; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); + } + } + + if (String.IsNullOrEmpty(productFamily)) + { + if (!String.IsNullOrEmpty(defaultProductFamily)) + { + productFamily = defaultProductFamily; + } + } + + if (String.IsNullOrEmpty(name)) + { + if (!String.IsNullOrEmpty(defaultName)) + { + name = defaultName; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); + } + } + + if (String.IsNullOrEmpty(classification)) + { + this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers) + { + Manufacturer = manufacturer, + Department = department, + ProductFamily = productFamily, + Name = name, + Classification = classification + }); + } + } + + /// + /// Parse Payload element. + /// + /// Element to parse + /// ComplexReferenceParentType of parent element. (BA or PayloadGroup) + /// Identifier of parent element. + /// + /// + private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) + { + Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the PayloadId is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child, context); + } + } + + compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); + + return compilerPayload.Id; + } + + /// + /// Parse PayloadGroup element. + /// + /// Element to parse + /// Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup) + /// Identifier of parent element. + private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId) + { + Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + var previousType = ComplexReferenceChildType.Unknown; + Identifier previousId = null; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + WixBundlePackageType? packageType = null; + switch (child.Name.LocalName) + { + case "ExePackagePayload": + packageType = WixBundlePackageType.Exe; + break; + case "MsiPackagePayload": + packageType = WixBundlePackageType.Msi; + break; + case "MspPackagePayload": + packageType = WixBundlePackageType.Msp; + break; + case "MsuPackagePayload": + packageType = WixBundlePackageType.Msu; + break; + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + + if (packageType.HasValue) + { + var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); + var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); + if (payloadSymbol != null) + { + previousId = payloadSymbol.Id; + previousType = ComplexReferenceChildType.Payload; + + this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); + } + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); + } + } + + /// + /// Parses a payload group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element (BA or PayloadGroup). + /// Identifier of parent element. + /// + /// + private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) + { + Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); + + return id; + } + + /// + /// Parse ExitCode element. + /// + /// Element to parse + /// Id of parent element + private void ParseExitCodeElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var value = CompilerConstants.IntegerNotSet; + var behavior = ExitCodeBehaviorType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); + break; + case "Behavior": + var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (behaviorString) + { + case "error": + behavior = ExitCodeBehaviorType.Error; + break; + case "forceReboot": + behavior = ExitCodeBehaviorType.ForceReboot; + break; + case "scheduleReboot": + behavior = ExitCodeBehaviorType.ScheduleReboot; + break; + case "success": + behavior = ExitCodeBehaviorType.Success; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); + behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below. + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (ExitCodeBehaviorType.NotSet == behavior) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers) + { + ChainPackageId = packageId, + Code = value, + Behavior = behavior + }); + } + } + + /// + /// Parse Chain element. + /// + /// Element to parse + private void ParseChainElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var attributes = WixChainAttributes.None; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "DisableRollback": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixChainAttributes.DisableRollback; + } + break; + case "DisableSystemRestore": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixChainAttributes.DisableSystemRestore; + } + break; + case "ParallelCache": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixChainAttributes.ParallelCache; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + string previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MsiPackage": + previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MspPackage": + previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MsuPackage": + previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "ExePackage": + previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "RollbackBoundary": + previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "PackageGroupRef": + previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.PackageGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (null == previousId) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers) + { + Attributes = attributes + }); + } + } + + /// + /// Parse MsiPackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); + } + + /// + /// Parse MspPackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); + } + + /// + /// Parse MsuPackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); + } + + /// + /// Parse ExePackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); + } + + /// + /// Parse RollbackBoundary element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var vital = YesNoType.Yes; + var transaction = YesNoType.No; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + if (id?.Id == BurnConstants.BundleDefaultBoundaryId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + break; + case "Vital": + vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Transaction": + transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + // Save the extension attributes for later... + extensionAttributes.Add(attrib); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(previousId)) + { + id = this.Core.CreateIdentifier("rba", previousId); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + + // Now that the rollback identifier is known, we can parse the extension attributes... + var contextValues = new Dictionary + { + ["RollbackBoundaryId"] = id.Id + }; + foreach (var attribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, attribute, contextValues); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); + } + + return id.Id; + } + + /// + /// Parses one of the ChainPackage elements + /// + /// Element to parse + /// Type of package to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + /// This method contains the shared logic for parsing all of the ChainPackage + /// types, as there is more in common between them than different. + private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + IsRequired = false, + }; + string after = null; + string installCondition = null; + var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. + string cacheId = null; + string description = null; + string displayName = null; + var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; + var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; + var permanent = YesNoType.NotSet; + var visible = YesNoType.NotSet; + var vital = YesNoType.Yes; + string installArguments = null; + string repairArguments = null; + string uninstallArguments = null; + var perMachine = YesNoDefaultType.NotSet; + string detectCondition = null; + string protocol = null; + var installSize = CompilerConstants.IntegerNotSet; + string msuKB = null; + var enableFeatureSelection = YesNoType.NotSet; + var forcePerMachine = YesNoType.NotSet; + CompilerPayload childPackageCompilerPayload = null; + var slipstream = YesNoType.NotSet; + var hasPayloadInfo = false; + + var expectedNetFx4Args = new string[] { "/q", "/norestart" }; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + hasPayloadInfo = true; + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + hasPayloadInfo = true; + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + hasPayloadInfo = true; + break; + case "After": + after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallCondition": + installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Cache": + var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (value) + { + case "always": + cache = YesNoAlwaysType.Always; + break; + case "yes": + cache = YesNoAlwaysType.Yes; + break; + case "no": + cache = YesNoAlwaysType.No; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); + break; + } + break; + case "CacheId": + cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EnableFeatureSelection": + enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msi); + break; + case "ForcePerMachine": + forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msi); + break; + case "LogPathVariable": + logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "RollbackLogPathVariable": + rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Permanent": + permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Visible": + visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msi); + break; + case "Vital": + vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "InstallArguments": + installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "RepairArguments": + repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "UninstallArguments": + uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "PerMachine": + perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); + break; + case "DetectCondition": + detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); + break; + case "Protocol": + protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "InstallSize": + installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "KB": + msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msu); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + hasPayloadInfo = true; + break; + case "Slipstream": + slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msp); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + // Save the extension attributes for later... + extensionAttributes.Add(attrib); + } + } + + // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. + var packagePayloadElementName = packageType + "PackagePayload"; + foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + if (childPackageCompilerPayload != null) + { + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + } + else if (hasPayloadInfo) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); + } + + childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); + } + + if (compilerPayload.Id == null && childPackageCompilerPayload != null) + { + compilerPayload.Id = childPackageCompilerPayload.Id; + } + + compilerPayload.FinishCompilingPackage(); + var id = compilerPayload.Id; + + if (id.Id == BurnConstants.BundleDefaultBoundaryId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + + if (null == logPathVariable) + { + logPathVariable = String.Concat("WixBundleLog_", id.Id); + } + + if (null == rollbackPathVariable) + { + rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); + } + + if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); + } + + if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) + { + foreach (var expectedArgument in expectedNetFx4Args) + { + if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); + } + + if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); + } + + if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); + } + } + } + + // Only set default scope for EXEs and MSPs if not already set. + if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) + { + perMachine = YesNoDefaultType.Default; + } + + // Detect condition is recommended or required for Exe and Msu packages + // (depending on whether uninstall arguments were provided). + if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) + { + if (String.IsNullOrEmpty(uninstallArguments)) + { + this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); + } + } + + // Now that the package ID is known, we can parse the extension attributes... + var contextValues = new Dictionary() { { "PackageId", id.Id } }; + foreach (var attribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, attribute, contextValues); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var allowed = true; + switch (child.Name.LocalName) + { + case "SlipstreamMsp": + allowed = (packageType == WixBundlePackageType.Msi); + if (allowed) + { + this.ParseSlipstreamMspElement(child, id.Id); + } + break; + case "MsiProperty": + allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); + if (allowed) + { + this.ParseMsiPropertyElement(child, id.Id); + } + break; + case "Payload": + this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); + break; + case "PayloadGroupRef": + this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); + break; + case "Provides": + this.ParseProvidesElement(child, packageType, id.Id, out _); + break; + case "ExitCode": + allowed = (packageType == WixBundlePackageType.Exe); + if (allowed) + { + this.ParseExitCodeElement(child, id.Id); + } + break; + case "CommandLine": + allowed = (packageType == WixBundlePackageType.Exe); + if (allowed) + { + this.ParseCommandLineElement(child, id.Id); + } + break; + case "ExePackagePayload": + case "MsiPackagePayload": + case "MspPackagePayload": + case "MsuPackagePayload": + allowed = packagePayloadElementName == child.Name.LocalName; + // Handled previously + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedElement(node, child); + } + } + else + { + var context = new Dictionary() { { "Id", id.Id } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError) + { + var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); + if (packageCompilerPayload != null) + { + var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); + + this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); + } + + this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); + + WixBundlePackageAttributes attributes = 0; + attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; + attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; + + var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) + { + Type = packageType, + Attributes = attributes, + InstallCondition = installCondition, + CacheId = cacheId, + Description = description, + DisplayName = displayName, + LogPathVariable = logPathVariable, + RollbackLogPathVariable = rollbackPathVariable, + }); + + if (YesNoAlwaysType.NotSet != cache) + { + chainPackageSymbol.Cache = cache; + } + + if (YesNoType.NotSet != vital) + { + chainPackageSymbol.Vital = (vital == YesNoType.Yes); + } + + if (YesNoDefaultType.NotSet != perMachine) + { + chainPackageSymbol.PerMachine = perMachine; + } + + if (CompilerConstants.IntegerNotSet != installSize) + { + chainPackageSymbol.InstallSize = installSize; + } + + switch (packageType) + { + case WixBundlePackageType.Exe: + this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id) + { + Attributes = WixBundleExePackageAttributes.None, + DetectCondition = detectCondition, + InstallCommand = installArguments, + RepairCommand = repairArguments, + UninstallCommand = uninstallArguments, + ExeProtocol = protocol + }); + break; + + case WixBundlePackageType.Msi: + WixBundleMsiPackageAttributes msiAttributes = 0; + msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; + msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; + + this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id) + { + Attributes = msiAttributes + }); + break; + + case WixBundlePackageType.Msp: + WixBundleMspPackageAttributes mspAttributes = 0; + mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; + + this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id) + { + Attributes = mspAttributes + }); + break; + + case WixBundlePackageType.Msu: + this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id) + { + DetectCondition = detectCondition, + MsuKB = msuKB + }); + break; + } + + this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null); + } + + return id.Id; + } + + private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) + { + switch (packageType) + { + case WixBundlePackageType.Exe: + this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msi: + this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msp: + this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msu: + this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + } + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); + } + + private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) + { + sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + Id = defaultId, + IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, + }; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + break; + case "Description": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseDescription(attrib); + } + break; + case "Hash": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseHash(attrib); + } + break; + case "ProductName": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseProductName(attrib); + } + break; + case "Size": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseSize(attrib); + } + break; + case "Version": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseVersion(attrib); + } + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + compilerPayload.FinishCompilingPackagePayload(); + + // Now that the PayloadId is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + this.Core.ParseForExtensionElements(node); + + return compilerPayload; + } + + /// + /// Parse CommandLine element. + /// + /// Element to parse + /// Parent packageId + private void ParseCommandLineElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string installArgument = null; + string uninstallArgument = null; + string repairArgument = null; + string condition = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "InstallArgument": + installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UninstallArgument": + uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "RepairArgument": + repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(condition)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers) + { + WixBundlePackageRef = packageId, + InstallArgument = installArgument, + UninstallArgument = uninstallArgument, + RepairArgument = repairArgument, + Condition = condition + }); + } + } + + /// + /// Parse PackageGroup element. + /// + /// Element to parse + private void ParsePackageGroupElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + if (id?.Id == BurnConstants.BundleChainPackageGroupId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + var previousType = ComplexReferenceChildType.Unknown; + string previousId = null; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MsiPackage": + previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MspPackage": + previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MsuPackage": + previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "ExePackage": + previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "RollbackBoundary": + previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "PackageGroupRef": + previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.PackageGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a package group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). + /// Identifier of parent element. + /// Identifier for package group element. + private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); + } + + /// + /// Parses a package group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). + /// Identifier of parent element. + /// + /// + /// Identifier for package group element. + private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string after = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + if (id == BurnConstants.BundleChainPackageGroupId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id)); + } + else + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id); + } + break; + case "After": + after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null != after && ComplexReferenceParentType.Container == parentType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); + } + + this.Core.ParseForExtensionElements(node); + + if (ComplexReferenceParentType.Container == parentType) + { + this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); + } + else + { + this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); + } + + return id; + } + + /// + /// Creates rollback boundary. + /// + /// Source line numbers. + /// Identifier for the rollback boundary. + /// Indicates whether the rollback boundary is vital or not. + /// Indicates whether the rollback boundary will use an MSI transaction. + /// Type of parent group. + /// Identifier of parent group. + /// Type of previous item, if any. + /// Identifier of previous item, if any. + private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); + + var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id)); + + if (YesNoType.NotSet != vital) + { + rollbackBoundary.Vital = (vital == YesNoType.Yes); + } + + if (YesNoType.NotSet != transaction) + { + rollbackBoundary.Transaction = (transaction == YesNoType.Yes); + } + + this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); + } + + /// + /// Creates group and ordering information for packages + /// + /// Source line numbers. + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of this item. + /// Identifier for this item. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier of explicit 'After' attribute, if given. + private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, + ComplexReferenceParentType parentType, string parentId, + ComplexReferenceChildType type, string id, + ComplexReferenceChildType previousType, string previousId, string afterId) + { + // If there's an explicit 'After' attribute, it overrides the inferred previous item. + if (null != afterId) + { + previousType = ComplexReferenceChildType.Package; + previousId = afterId; + } + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); + } + + /// + /// Parse MsiProperty element + /// + /// Element to parse + /// Id of parent element + private void ParseMsiPropertyElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string value = null; + string condition = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name)) + { + PackageRef = packageId, + Name = name, + Value = value + }); + + if (!String.IsNullOrEmpty(condition)) + { + symbol.Condition = condition; + } + } + } + + /// + /// Parse SlipstreamMsp element + /// + /// Element to parse + /// Id of parent element + private void ParseSlipstreamMspElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id)) + { + TargetPackageRef = packageId, + MspPackageRef = id + }); + } + } + + /// + /// Parse RelatedBundle element + /// + /// Element to parse + private void ParseRelatedBundleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var actionType = RelatedBundleActionType.Detect; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Action": + var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (action) + { + case "Detect": + case "detect": + actionType = RelatedBundleActionType.Detect; + break; + case "Upgrade": + case "upgrade": + actionType = RelatedBundleActionType.Upgrade; + break; + case "Addon": + case "addon": + actionType = RelatedBundleActionType.Addon; + break; + case "Patch": + case "patch": + actionType = RelatedBundleActionType.Patch; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) + { + BundleId = id, + Action = actionType, + }); + } + } + + /// + /// Parse Update element + /// + /// Element to parse + private void ParseUpdateElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string location = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Location": + location = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == location) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers) + { + Location = location + }); + } + } + + /// + /// Parse SetVariable element + /// + /// Element to parse + private void ParseSetVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string variable = null; + string condition = null; + string after = null; + string value = null; + string typeValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Type": + typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib, null); + } + } + + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); + + this.Core.ParseForExtensionElements(node); + + if (id == null) + { + id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); + } + + this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); + + if (!this.Messaging.EncounteredError) + { + this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id) + { + Value = value, + Type = type, + }); + } + } + + /// + /// Parse Variable element + /// + /// Element to parse + private void ParseVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var hidden = false; + string name = null; + var persisted = false; + string value = null; + string typeValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + hidden = true; + } + break; + case "Name": + name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); + break; + case "Persisted": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + persisted = true; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Type": + typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); + } + + if (hidden && persisted) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); + } + + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) + { + Value = value, + Type = type, + Hidden = hidden, + Persisted = persisted + }); + } + } + + private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) + { + WixBundleVariableType type; + switch (typeValue) + { + case "formatted": + type = WixBundleVariableType.Formatted; + break; + case "numeric": + type = WixBundleVariableType.Numeric; + break; + case "string": + type = WixBundleVariableType.String; + break; + case "version": + type = WixBundleVariableType.Version; + break; + case null: + type = WixBundleVariableType.Unknown; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); + return WixBundleVariableType.Unknown; + } + + if (type != WixBundleVariableType.Unknown) + { + if (value == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); + } + + return type; + } + else if (value == null) + { + return type; + } + + // Infer the type from the current value... + if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + { + // Version constructor does not support simple "v#" syntax so check to see if the value is + // non-negative real quick. + if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) + { + return WixBundleVariableType.Version; + } + else if (Version.TryParse(value.Substring(1), out var _)) + { + return WixBundleVariableType.Version; + } + } + + // Not a version, check for numeric. + if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) + { + return WixBundleVariableType.Numeric; + } + + return WixBundleVariableType.String; + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Dependency.cs b/src/wix/WixToolset.Core/Compiler_Dependency.cs new file mode 100644 index 00000000..7c863883 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Dependency.cs @@ -0,0 +1,384 @@ +// 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.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + // The root registry key for the dependency extension. We write to Software\Classes explicitly + // based on the current security context instead of HKCR. See + // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. + private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; + + private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' }; + + /// + /// Processes the ProviderKey bundle attribute. + /// + /// Source line number for the parent element. + /// Parent element of attribute. + /// The XML attribute for the ProviderKey attribute. + private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute) + { + var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + int illegalChar; + + // Make sure the key does not contain any illegal characters or values. + if (String.IsNullOrEmpty(providerKey)) + { + this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName)); + } + else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + else if ("ALL" == providerKey) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey)); + } + + if (!this.Messaging.EncounteredError) + { + // Generate the primary key for the row. + var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey); + + // Create the provider symbol for the bundle. The Component_ field is required + // in the table definition but unused for bundles, so just set it to the valid ID. + this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) + { + ParentRef = id.Id, + ProviderKey = providerKey, + Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle, + }); + } + } + + /// + /// Processes the Provides element. + /// + /// The XML node for the Provides element. + /// The type of the package being chained into a bundle, or null if building an MSI package. + /// The identifier of the parent component or package. + /// Possible KeyPath identifier. + /// Yes if this is the keypath. + private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath) + { + possibleKeyPath = null; + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string version = null; + string displayName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Make sure the key is valid. The key will default to the ProductCode for MSI packages + // and the package code for MSP packages in the binder if not specified. + if (!String.IsNullOrEmpty(key)) + { + int illegalChar; + + // Make sure the key does not contain any illegal characters or values. + if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + else if ("ALL" == key) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key)); + } + } + else if (!packageType.HasValue) + { + // Make sure the ProductCode is authored and set the key. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode"); + key = "!(bind.property.ProductCode)"; + } + else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType) + { + // Must specify the provider key when authored for a package. + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + // The Version attribute should not be authored in or for an MSI package. + if (!String.IsNullOrEmpty(version)) + { + switch (packageType) + { + case null: + this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers)); + break; + case WixBundlePackageType.Msi: + this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId)); + break; + } + } + else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType) + { + // Must specify the Version when authored for packages that do not contain a version. + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + + // Need the element ID for child element processing, so generate now if not authored. + if (null == id) + { + id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Requires": + this.ParseRequiresElement(child, id.Id); + break; + case "RequiresRef": + this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Messaging.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) + { + ParentRef = parentId, + ProviderKey = key, + }); + + if (!String.IsNullOrEmpty(version)) + { + symbol.Version = version; + } + + if (!String.IsNullOrEmpty(displayName)) + { + symbol.DisplayName = displayName; + } + + if (!packageType.HasValue) + { + // Generate registry rows for the provider using binder properties. + var keyProvides = String.Concat(DependencyRegistryRoot, key); + var root = RegistryRootType.MachineUser; + + var value = "[ProductCode]"; + this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId); + + value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]"; + var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId); + + value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]"; + this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId); + + // Use the Version registry value and use that as a potential key path. + possibleKeyPath = versionRegistrySymbol.Id; + } + } + + return YesNoType.NotSet; + } + + /// + /// Processes the Requires element. + /// + /// The XML node for the Requires element. + /// The parent provider identifier. + private void ParseRequiresElement(XElement node, string providerId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string providerKey = null; + string minVersion = null; + string maxVersion = null; + var attributes = WixDependencySymbolAttributes.None; + var illegalChar = -1; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProviderKey": + providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive; + } + break; + case "IncludeMaximum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (null == id) + { + // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef + // element will be necessary and the Id attribute will be required. + if (!String.IsNullOrEmpty(providerId)) + { + id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey); + } + else + { + this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides")); + id = Identifier.Invalid; + } + } + + if (String.IsNullOrEmpty(providerKey)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey")); + } + // Make sure the key does not contain any illegal characters. + else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + + if (!this.Messaging.EncounteredError) + { + this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id) + { + ProviderKey = providerKey, + MinVersion = minVersion, + MaxVersion = maxVersion, + Attributes = attributes + }); + + // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol. + if (!String.IsNullOrEmpty(providerId)) + { + this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) + { + WixDependencyProviderRef = providerId, + WixDependencyRef = id.Id, + }); + } + } + } + + /// + /// Processes the RequiresRef element. + /// + /// The XML node for the RequiresRef element. + /// The parent provider identifier. + /// Whether the Requires custom action should be referenced. + private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(id)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + // Create a link dependency on the row that contains information we'll need during bind. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id); + + // Create the relationship between the WixDependency row and the parent WixDependencyProvider row. + this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) + { + WixDependencyProviderRef = providerId, + WixDependencyRef = id, + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs new file mode 100644 index 00000000..ede03933 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs @@ -0,0 +1,417 @@ +// 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.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses an EmbeddedChaniner element. + /// + /// Element to parse. + private void ParseEmbeddedChainerElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string commandLine = null; + string condition = null; + string source = null; + var type = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "BinarySource": + if (null != source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + type = 0x2; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary + break; + case "CommandLine": + commandLine = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "FileSource": + if (null != source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + type = 0x12; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File + break; + case "PropertySource": + if (null != source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + type = 0x32; + // cannot add a reference to a Property because it may be created at runtime. + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("mec", source, type.ToString()); + } + + if (null == source) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiEmbeddedChainerSymbol(sourceLineNumbers, id) + { + Condition = condition, + CommandLine = commandLine, + Source = source, + Type = type + }); + } + } + + /// + /// Parses an EmbeddedUI element. + /// + /// Element to parse. + private void ParseEmbeddedUIElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + var supportsBasicUI = false; + var messageFilter = WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT | WindowsInstallerConstants.INSTALLLOGMODE_ERROR | WindowsInstallerConstants.INSTALLLOGMODE_WARNING | WindowsInstallerConstants.INSTALLLOGMODE_USER + | WindowsInstallerConstants.INSTALLLOGMODE_INFO | WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE | WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE + | WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA + | WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS | WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA | WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE + | WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE | WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG | WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE + | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "IgnoreFatalExit": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT; + } + break; + case "IgnoreError": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ERROR; + } + break; + case "IgnoreWarning": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_WARNING; + } + break; + case "IgnoreUser": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_USER; + } + break; + case "IgnoreInfo": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INFO; + } + break; + case "IgnoreFilesInUse": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE; + } + break; + case "IgnoreResolveSource": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE; + } + break; + case "IgnoreOutOfDiskSpace": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE; + } + break; + case "IgnoreActionStart": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART; + } + break; + case "IgnoreActionData": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA; + } + break; + case "IgnoreProgress": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS; + } + break; + case "IgnoreCommonData": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA; + } + break; + case "IgnoreInitialize": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE; + } + break; + case "IgnoreTerminate": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE; + } + break; + case "IgnoreShowDialog": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG; + } + break; + case "IgnoreRMFilesInUse": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE; + } + break; + case "IgnoreInstallStart": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART; + } + break; + case "IgnoreInstallEnd": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; + } + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SupportBasicUI": + supportsBasicUI = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(sourceFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + else if (String.IsNullOrEmpty(name)) + { + name = Path.GetFileName(sourceFile); + if (!this.Core.IsValidLongFilename(name, false)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(name)) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + else if (String.IsNullOrEmpty(name)) + { + name = id.Id; + } + + if (!name.Contains(".")) + { + this.Core.Write(ErrorMessages.InvalidEmbeddedUIFileName(sourceLineNumbers, name)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "EmbeddedUIResource": + this.ParseEmbeddedUIResourceElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) + { + FileName = name, + EntryPoint = true, + SupportsBasicUI = supportsBasicUI, + MessageFilter = messageFilter, + Source = sourceFile + }); + } + } + + /// + /// Parses a embedded UI resource element. + /// + /// Element to parse. + private void ParseEmbeddedUIResourceElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(sourceFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + else if (String.IsNullOrEmpty(name)) + { + name = Path.GetFileName(sourceFile); + if (!this.Core.IsValidLongFilename(name, false)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(name)) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + else if (String.IsNullOrEmpty(name)) + { + name = id.Id; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) + { + FileName = name, + Source = sourceFile + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Module.cs b/src/wix/WixToolset.Core/Compiler_Module.cs new file mode 100644 index 00000000..3986c8da --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Module.cs @@ -0,0 +1,662 @@ +// 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.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a module element. + /// + /// Element to parse. + private void ParseModuleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var codepage = 0; + string moduleId = null; + string version = null; + var setCodepage = false; + var setPackageName = false; + var setKeywords = false; + var ignoredForMergeModules = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-MODULE-NAME-HERE" == this.activeName) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); + } + else + { + this.activeName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + } + break; + case "Codepage": + codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + break; + case "Guid": + moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Language": + this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == moduleId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid")); + } + + if (null == this.activeLanguage) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + } + + if (null == version) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) + { + this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version)); + } + + try + { + this.compilingModule = true; // notice that we are actually building a Merge Module here + this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); + break; + case "Configuration": + this.ParseConfigurationElement(child); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Dependency": + this.ParseDependencyElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Exclusion": + this.ParseExclusionElement(child); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "IgnoreTable": + this.ParseIgnoreTableElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; + case "Substitution": + this.ParseSubstitutionElement(child); + break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules); + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + if (!setPackageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!setKeywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + + var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) + { + ModuleId = this.activeName, + Language = this.activeLanguage, + Version = version + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PackageCode, + Value = moduleId + }); + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage); + } + } + finally + { + this.compilingModule = false; // notice that we are no longer building a Merge Module here + } + } + + /// + /// Parses a dependency element. + /// + /// Element to parse. + private void ParseDependencyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string requiredId = null; + var requiredLanguage = CompilerConstants.IntegerNotSet; + string requiredVersion = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "RequiredId": + requiredId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "RequiredLanguage": + requiredLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "RequiredVersion": + requiredVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == requiredId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId")); + requiredId = String.Empty; + } + + if (CompilerConstants.IntegerNotSet == requiredLanguage) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage")); + requiredLanguage = CompilerConstants.IllegalInteger; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ModuleDependencySymbol(sourceLineNumbers) + { + ModuleID = this.activeName, + RequiredID = requiredId, + RequiredLanguage = requiredLanguage, + RequiredVersion = requiredVersion + }); + + symbol.Set((int)ModuleDependencySymbolFields.ModuleLanguage, this.activeLanguage); + } + } + + /// + /// Parses an exclusion element. + /// + /// Element to parse. + private void ParseExclusionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string excludedId = null; + var excludeExceptLanguage = CompilerConstants.IntegerNotSet; + var excludeLanguage = CompilerConstants.IntegerNotSet; + var excludedLanguageField = "0"; + string excludedMaxVersion = null; + string excludedMinVersion = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludedId": + excludedId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ExcludeExceptLanguage": + excludeExceptLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "ExcludeLanguage": + excludeLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "ExcludedMaxVersion": + excludedMaxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ExcludedMinVersion": + excludedMinVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == excludedId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId")); + excludedId = String.Empty; + } + + if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage) + { + this.Core.Write(ErrorMessages.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers)); + } + else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage) + { + excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture); + } + else if (CompilerConstants.IntegerNotSet != excludeLanguage) + { + excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ModuleExclusionSymbol(sourceLineNumbers) + { + ModuleID = this.activeName, + ExcludedID = excludedId, + ExcludedMinVersion = excludedMinVersion, + ExcludedMaxVersion = excludedMaxVersion + }); + + symbol.Set((int)ModuleExclusionSymbolFields.ModuleLanguage, this.activeLanguage); + symbol.Set((int)ModuleExclusionSymbolFields.ExcludedLanguage, excludedLanguageField); + } + } + + /// + /// Parses a configuration element for a configurable merge module. + /// + /// Element to parse. + private void ParseConfigurationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string contextData = null; + string defaultValue = null; + string description = null; + string displayName = null; + var format = CompilerConstants.IntegerNotSet; + string helpKeyword = null; + string helpLocation = null; + bool keyNoOrphan = false; + bool nonNullable = false; + Identifier name = null; + string type = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ContextData": + contextData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DefaultValue": + defaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Format": + var formatStr = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (formatStr) + { + case "Text": + case "text": + format = 0; + break; + case "Key": + case "key": + format = 1; + break; + case "Integer": + case "integer": + format = 2; + break; + case "Bitfield": + case "bitfield": + format = 3; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield")); + break; + } + break; + case "HelpKeyword": + helpKeyword = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HelpLocation": + helpLocation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "KeyNoOrphan": + keyNoOrphan = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "NonNullable": + nonNullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Type": + type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (CompilerConstants.IntegerNotSet == format) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ModuleConfigurationSymbol(sourceLineNumbers, name) + { + Format = format, + Type = type, + ContextData = contextData, + DefaultValue = defaultValue, + KeyNoOrphan = keyNoOrphan, + NonNullable = nonNullable, + DisplayName = displayName, + Description = description, + HelpLocation = helpLocation, + HelpKeyword = helpKeyword + }); + } + } + + /// + /// Parses a substitution element for a configurable merge module. + /// + /// Element to parse. + private void ParseSubstitutionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string column = null; + string rowKeys = null; + string table = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Column": + column = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Row": + rowKeys = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Table": + table = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == column) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column")); + column = String.Empty; + } + + if (null == table) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table")); + table = String.Empty; + } + + if (null == rowKeys) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ModuleSubstitutionSymbol(sourceLineNumbers) + { + Table = table, + Row = rowKeys, + Column = column, + Value = value + }); + } + } + + /// + /// Parses an IgnoreTable element. + /// + /// Element to parse. + private void ParseIgnoreTableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ModuleIgnoreTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, id))); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Package.cs b/src/wix/WixToolset.Core/Compiler_Package.cs new file mode 100644 index 00000000..87ccceb7 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Package.cs @@ -0,0 +1,4996 @@ +// 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.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a product element. + /// + /// Element to parse. + private void ParsePackageElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compressed = YesNoDefaultType.Default; + var sourceBits = 0; + string codepage = null; + var productCode = "*"; + string productLanguage = null; + var isPerMachine = true; + string upgradeCode = null; + string manufacturer = null; + string version = null; + string symbols = null; + var isCodepageSet = false; + var isPackageNameSet = false; + var isKeywordsSet = false; + var isPackageAuthorSet = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + case "Compressed": + compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Language": + productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-COMPANY-NAME-HERE" == manufacturer) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); + } + break; + case "Name": + this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-PRODUCT-NAME-HERE" == this.activeName) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); + } + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Scope": + var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installScope) + { + case "perMachine": + // handled below after we create the section. + break; + case "perUser": + isPerMachine = false; + sourceBits |= 8; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); + break; + } + break; + case "ShortNames": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + sourceBits |= 1; + } + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). + var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + if (!String.IsNullOrEmpty(verifiedVersion)) + { + version = attrib.Value; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == productCode) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == manufacturer) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == upgradeCode) + { + this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); + } + + if (null == version) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidProductVersion(version)) + { + this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); + } + + if (compressed != YesNoDefaultType.No) + { + sourceBits |= 2; + } + + if (this.Core.EncounteredError) + { + return; + } + + try + { + this.compilingProduct = true; + this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId); + + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); + if (null != upgradeCode) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); + } + + if (isPerMachine) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); + } + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = sourceBits.ToString(CultureInfo.InvariantCulture) + }); + + var contextValues = new Dictionary + { + ["ProductLanguage"] = productLanguage, + ["ProductVersion"] = version, + ["UpgradeCode"] = upgradeCode + }; + + var featureDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "_locDefinition": + break; + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "ComplianceCheck": + this.ParseComplianceCheckElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroup": + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "InstanceTransforms": + this.ParseInstanceTransformsElement(child); + break; + case "Launch": + this.ParseLaunchElement(child); + break; + case "MajorUpgrade": + this.ParseMajorUpgradeElement(child, contextValues); + break; + case "Media": + this.ParseMediaElement(child, null); + break; + case "MediaTemplate": + this.ParseMediaTemplateElement(child, null); + break; + case "PackageCertificates": + case "PatchCertificates": + this.ParseCertificatesElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "SoftwareTag": + this.ParsePackageTagElement(child); + break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "Upgrade": + this.ParseUpgradeElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers) + { + PackageId = productCode, + UpgradeCode = upgradeCode, + Name = this.activeName, + Language = productLanguage, + Version = version, + Manufacturer = manufacturer, + Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None, + Codepage = codepage, + }); + + if (!isPackageNameSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!isPackageAuthorSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = manufacturer + }); + } + + if (!isKeywordsSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) + { + SymbolId = productCode, + SymbolType = SymbolPathType.Product, + SymbolPaths = symbols, + }); + } + } + } + finally + { + this.compilingProduct = false; + } + } + + private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) + { + // Let's default to a modern version of MSI. Users can override, + // of course, subject to platform-specific limitations. + msiVersion = 500; + + switch (this.CurrentPlatform) + { + case Platform.X86: + platform = "Intel"; + break; + case Platform.X64: + platform = "x64"; + break; + case Platform.ARM64: + platform = "Arm64"; + break; + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); + } + } + + private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language) + { + if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) + { + msiVersion = 200; + this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); + } + + if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) + { + msiVersion = 500; + this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Installation Database" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PlatformAndLanguage, + Value = $"{platform};{language}" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = "2" + }); + } + + /// + /// Parses an odbc driver or translator element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default identifer for driver/translator file. + /// Symbol type we're processing for. + private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var driver = fileId; + string name = null; + var setup = fileId; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "File": + driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SetupFile": + setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odb", name, fileId, setup); + } + + // drivers have a few possible children + if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) + { + // process any data sources for the driver + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ODBCDataSource": + string ignoredKeyPath = null; + this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); + break; + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + else + { + this.Core.ParseForExtensionElements(node); + } + + if (!this.Core.EncounteredError) + { + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCDriver: + this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + case SymbolDefinitionType.ODBCTranslator: + this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parses a Property element underneath an ODBC driver or translator. + /// + /// Element to parse. + /// Identifier of parent driver or translator. + /// Name of the table to create property in. + private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string propertyValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var identifier = new Identifier(AccessModifier.Section, parentId, id); + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCAttribute: + this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) + { + DriverRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + case SymbolDefinitionType.ODBCSourceAttribute: + this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) + { + DataSourceRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parse an odbc data source element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default name of driver. + /// Identifier of this element in case it is a keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var keyPath = YesNoType.NotSet; + string name = null; + var registration = CompilerConstants.IntegerNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DriverName": + driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Registration": + var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (registrationValue) + { + case "machine": + registration = 0; + break; + case "user": + registration = 1; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == registration) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); + registration = CompilerConstants.IllegalInteger; + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + DriverDescription = driverName, + Registration = registration + }); + } + + possibleKeyPath = id.Id; + return keyPath; + } + + /// + /// Parses a package element. + /// + /// Element to parse. + /// + /// + /// + /// + private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string codepage = null; + string packageName = null; + string keywords = null; + string packageAuthor = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-COMPANY-NAME-HERE" == packageAuthor) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (null != codepage) + { + isCodepageSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + } + + if (null != packageName) + { + isPackageNameSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + isPackageAuthorSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + isKeywordsSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + } + } + + /// + /// Parses a patch information element. + /// + /// Element to parse. + private void ParsePatchInformationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var codepage = "1252"; + string comments = null; + var keywords = "Installer,Patching,PCP,Database"; + var msiVersion = 1; // Should always be 1 for patches + string packageAuthor = null; + var packageName = this.activeName; + var security = YesNoDefaultType.Default; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AdminImage": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Comments": + comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Compressed": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Languages": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Platforms": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "ReadOnly": + security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "ShortNames": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "SummaryCodepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Patch" + }); + + if (null != packageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + + if (null != comments) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = comments + }); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = "0" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" + }); + } + } + + /// + /// Parses a permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var bits = new BitArray(32); + string domain = null; + string[] specialPermissions = null; + string user = null; + + switch (tableName) + { + case "CreateFolder": + specialPermissions = LockPermissionConstants.FolderPermissions; + break; + case "File": + specialPermissions = LockPermissionConstants.FilePermissions; + break; + case "Registry": + specialPermissions = LockPermissionConstants.RegistryPermissions; + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since no valid permissions are available + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Domain": + domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "User": + user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "FileAllRights": + // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; + break; + case "SpecificRightsAll": + // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; + break; + default: + var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (!this.Core.TrySetBitFromName(LockPermissionConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) + { + if (!this.Core.TrySetBitFromName(LockPermissionConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) + { + if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) + { + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == user) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); + } + + var permission = this.Core.CreateIntegerFromBitArray(bits); + + if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL + { + this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) + { + LockObject = objectId, + Table = tableName, + Domain = domain, + User = user, + Permission = permission + }); + } + } + + /// + /// Parses an extended permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionExElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + Identifier id = null; + string sddl = null; + + switch (tableName) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since nothing will be valid. + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sddl": + sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == sddl) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) + { + LockObject = objectId, + Table = tableName, + SDDLText = sddl, + Condition = condition + }); + } + } + + /// + /// Parses a progid element + /// + /// Element to parse. + /// Identifier of parent component. + /// Flag if progid is advertised. + /// CLSID related to ProgId. + /// Default description of ProgId + /// Optional parent ProgId + /// Set to true if an extension is found; used for error-checking. + /// Whether or not this ProgId is the first one found in the parent class. + /// This element's Id. + private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string icon = null; + var iconIndex = CompilerConstants.IntegerNotSet; + string noOpen = null; + string progId = null; + var progIdAdvertise = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Advertise": + progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "NoOpen": + noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); + } + else if (YesNoType.NotSet != progIdAdvertise) + { + advertise = progIdAdvertise; + } + + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) + { + this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); + } + + var firstProgIdForNestedClass = YesNoType.Yes; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Extension": + this.ParseExtensionElement(child, componentId, advertise, progId); + foundExtension = true; + break; + case "ProgId": + // Only allow one nested ProgId. If we have a child, we should not have a parent. + if (null == parent) + { + if (YesNoType.Yes == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + else if (YesNoType.No == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + + firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (YesNoType.Yes == advertise) + { + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, progId)) + { + ProgId = progId, + ParentProgIdRef = parent, + ClassRef = classId, + Description = description, + }); + + if (null != icon) + { + symbol.IconRef = icon; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + } + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + symbol.IconIndex = iconIndex; + } + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); + } + } + else if (YesNoType.No == advertise) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); + if (null != classId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); + if (null != parent) // if this is a version independent ProgId + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); + } + else + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); + } + } + } + + if (null != icon) // ProgId's Default Icon + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); + + icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + icon = String.Concat(icon, ",", iconIndex); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); + } + } + + if (null != noOpen) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name + } + + // raise an error for an orphaned ProgId + if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) + { + this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); + } + + return progId; + } + + /// + /// Parses a property element. + /// + /// Element to parse. + private void ParsePropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var admin = false; + var complianceCheck = false; + var hidden = false; + var secure = false; + var suppressModularization = YesNoType.NotSet; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Admin": + admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ComplianceCheck": + complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Secure": + secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if ("ProductID" == id.Id) + { + this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); + } + else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) + { + this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); + } + + if ("ErrorDialog" == id.Id) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + { + switch (child.Name.LocalName) + { + case "ProductSearch": + this.ParseProductSearchElement(child, id.Id); + secure = true; + break; + default: + // let ParseSearchSignatures handle standard AppSearch children and unknown elements + break; + } + } + } + } + + this.Core.InnerTextDisallowed(node); + + // see if this property is used for appSearch + var signatures = this.ParseSearchSignatures(node); + + // If we're doing CCP then there must be a signature. + if (complianceCheck && 0 == signatures.Count) + { + this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); + } + + foreach (var sig in signatures) + { + if (complianceCheck && !this.Core.EncounteredError) + { + this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, sig))); + } + + this.AddAppSearch(sourceLineNumbers, id, sig); + } + + // If we're doing AppSearch get that setup. + if (0 < signatures.Count) + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + else // just a normal old property. + { + // If the property value is empty and none of the flags are set, print out a warning that we're ignoring + // the element. + if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) + { + this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); + } + else // there is a value and/or a flag set, do that. + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + } + + if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) + { + this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); + + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); + } + } + + /// + /// Parses a RegistryKey element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. + /// Parent key for this Registry element when nested. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + var forceCreateOnInstall = false; + var forceDeleteOnUninstall = false; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ForceCreateOnInstall": + forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ForceDeleteOnUninstall": + forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + key = Path.Combine(parentKey, key); + } + key = key?.TrimEnd('\\'); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); + + if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present + { + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + } + else // does not generate a Registry row, so no Id should be present + { + if (null != id) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); + } + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + key = String.Empty; // set the key to something to prevent null reference exceptions + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + string possibleChildKeyPath = null; + + switch (child.Name.LocalName) + { + case "RegistryKey": + if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "RegistryValue": + if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "Permission": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError && null != name) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId, + }); + } + + return keyPath; + } + + /// + /// Parses a RegistryValue element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + string name = null; + string value = null; + string action = null; + var valueType = RegistryValueType.String; + var actionType = RegistryValueActionType.Write; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "append": + actionType = RegistryValueActionType.Append; + break; + case "prepend": + actionType = RegistryValueActionType.Prepend; + break; + case "write": + actionType = RegistryValueActionType.Write; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + if (parentKey.EndsWith("\\", StringComparison.Ordinal)) + { + key = String.Concat(parentKey, key); + } + else + { + key = String.Concat(parentKey, "\\", key); + } + } + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "binary": + valueType = RegistryValueType.Binary; + break; + case "expandable": + valueType = RegistryValueType.Expandable; + break; + case "integer": + valueType = RegistryValueType.Integer; + break; + case "multiString": + valueType = RegistryValueType.MultiString; + break; + case "string": + valueType = RegistryValueType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MultiString": + case "MultiStringValue": + if (RegistryValueType.MultiString != valueType && null != value) + { + this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); + } + else + { + value = this.ParseRegistryMultiStringElement(child, value); + } + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + //switch (typeType) + //{ + //case Wix.RegistryValue.TypeType.binary: + // value = String.Concat("#x", value); + // break; + //case Wix.RegistryValue.TypeType.expandable: + // value = String.Concat("#%", value); + // break; + //case Wix.RegistryValue.TypeType.integer: + // value = String.Concat("#", value); + // break; + //case Wix.RegistryValue.TypeType.multiString: + // switch (actionType) + // { + // case Wix.RegistryValue.ActionType.append: + // value = String.Concat("[~]", value); + // break; + // case Wix.RegistryValue.ActionType.prepend: + // value = String.Concat(value, "[~]"); + // break; + // case Wix.RegistryValue.ActionType.write: + // default: + // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + // { + // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + // } + // break; + // } + // break; + //case Wix.RegistryValue.TypeType.@string: + // // escape the leading '#' character for string registry keys + // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + // { + // value = String.Concat("#", value); + // } + // break; + //} + + // value may be set by child MultiStringValue elements, so it must be checked here + if (null == value && valueType != RegistryValueType.Binary) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values + { + this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Value = value, + ValueType = valueType, + ValueAction = actionType, + ComponentRef = componentId, + }); + } + + // If this was just a regular registry key (that could be the key path) + // and no child registry key set the possible key path, let's make this + // Registry/@Id a possible key path. + if (null == possibleKeyPath) + { + possibleKeyPath = id.Id; + } + + return keyPath; + } + + private string ParseRegistryMultiStringElement(XElement node, string value) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string multiStringValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.ParseForExtensionElements(node); + + return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); + } + + /// + /// Parses a RemoveRegistryKey element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryKeyElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + RemoveRegistryActionType? actionType = null; + string key = null; + var name = "-"; + RegistryRootType? root = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "removeOnInstall": + actionType = RemoveRegistryActionType.RemoveOnInstall; + break; + case "removeOnUninstall": + actionType = RemoveRegistryActionType.RemoveOnUninstall; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); + break; + } + //if (0 < action.Length) + //{ + // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) + // { + // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); + // } + //} + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!actionType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Action = actionType.Value, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a RemoveRegistryValue element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryValueElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string name = null; + RegistryRootType? root = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId + }); + } + } + + /// + /// Parses a remove file element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of the parent component's directory. + private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directoryId = null; + string subdirectory = null; + string name = null; + bool? onInstall = null; + bool? onUninstall = null; + string propertyId = null; + string shortName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (String.IsNullOrEmpty(propertyId)) + { + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); + } + else if (!String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); + } + else if (!String.IsNullOrEmpty(subdirectory)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FileName = name, + ShortFileName = shortName, + DirPropertyRef = directoryId ?? propertyId ?? parentDirectory, + OnInstall = onInstall, + OnUninstall = onUninstall, + }); + } + } + + /// + /// Parses a RemoveFolder element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of parent component's directory. + private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directoryId = null; + string subdirectory = null; + bool? onInstall = null; + bool? onUninstall = null; + string propertyId = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (String.IsNullOrEmpty(propertyId)) + { + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); + } + else if (!String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); + } + else if (!String.IsNullOrEmpty(subdirectory)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + DirPropertyRef = directoryId ?? propertyId, + OnInstall = onInstall, + OnUninstall = onUninstall + }); + } + } + + /// + /// Parses a reserve cost element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional and default identifier of referenced directory. + private void ParseReserveCostElement(XElement node, string componentId, string directoryId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string subdirectory = null; + var runFromSource = CompilerConstants.IntegerNotSet; + var runLocal = CompilerConstants.IntegerNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "RunFromSource": + runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "RunLocal": + runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + if (null == id) + { + id = this.Core.CreateIdentifier("rc", componentId, directoryId); + } + + if (CompilerConstants.IntegerNotSet == runFromSource) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); + } + + if (CompilerConstants.IntegerNotSet == runLocal) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + ReserveFolder = directoryId, + ReserveLocal = runLocal, + ReserveSource = runFromSource + }); + } + } + + /// + /// Parses a sequence element. + /// + /// Element to parse. + /// Name of sequence table. + private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) + { + // Parse each action in the sequence. + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + var actionName = child.Name.LocalName; + string afterAction = null; + string beforeAction = null; + string condition = null; + var customAction = "Custom" == actionName; + var overridable = false; + var exitSequence = CompilerConstants.IntegerNotSet; + var sequence = CompilerConstants.IntegerNotSet; + var showDialog = "Show" == actionName; + var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; + var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; + var suppress = false; + + foreach (var attrib in child.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + if (customAction) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "After": + if (customAction || showDialog || specialAction || specialStandardAction) + { + afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Before": + if (customAction || showDialog || specialAction || specialStandardAction) + { + beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Condition": + condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Dialog": + if (showDialog) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "OnExit": + if (customAction || showDialog || specialAction) + { + var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + switch (exitValue) + { + case "success": + exitSequence = -1; + break; + case "cancel": + exitSequence = -2; + break; + case "error": + exitSequence = -3; + break; + case "suspend": + exitSequence = -4; + break; + } + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Overridable": + overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Suppress": + suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (customAction && "Custom" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); + } + else if (showDialog && "Show" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); + } + + if (CompilerConstants.IntegerNotSet != sequence) + { + if (CompilerConstants.IntegerNotSet != exitSequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); + } + else if (null != beforeAction || null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); + } + } + else // sequence not specified use OnExit (which may also be not set). + { + sequence = exitSequence; + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); + } + else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); + } + + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); + } + + // normal standard actions cannot be set overridable by the user (since they are overridable by default) + if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) + { + this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); + } + + // suppress cannot be specified at the same time as Before, After, or Sequence + if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); + } + + this.Core.ParseForExtensionElements(child); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + if (suppress) + { + this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName + }); + } + else + { + var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName, + Condition = condition, + Before = beforeAction, + After = afterAction, + Overridable = overridable, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + } + + this.Core.InnerTextDisallowed(node); + } + + + /// + /// Parses a service config element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string delayedAutoStart = null; + string failureActionsWhen = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + string preShutdownDelay = null; + string requiredPrivileges = null; + string sid = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DelayedAutoStart": + delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (delayedAutoStart) + { + case "no": + delayedAutoStart = "0"; + break; + case "yes": + delayedAutoStart = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "FailureActionsWhen": + failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (failureActionsWhen) + { + case "failedToStop": + failureActionsWhen = "0"; + break; + case "failedToStopOrReturnedError": + failureActionsWhen = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == install) + //{ + // events |= MsiInterop.MsidbServiceConfigEventInstall; + //} + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == reinstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventReinstall; + //} + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == uninstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventUninstall; + //} + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + case "PreShutdownDelay": + preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ServiceSid": + sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sid) + { + case "none": + sid = "0"; + break; + case "restricted": + sid = "3"; + break; + case "unrestricted": + sid = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfig required privilegs. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "RequiredPrivilege": + requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); + } + + if (!this.Core.EncounteredError) + { + if (!String.IsNullOrEmpty(delayedAutoStart)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.DelayedAutoStart, + Argument = delayedAutoStart, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(failureActionsWhen)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.FailureActionsFlag, + Argument = failureActionsWhen, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(sid)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.ServiceSidInfo, + Argument = sid, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(requiredPrivileges)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, + Argument = requiredPrivileges, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(preShutdownDelay)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.PreshutdownInfo, + Argument = preShutdownDelay, + ComponentRef = componentId, + }); + } + } + } + + private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string privilege = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (privilege) + { + case "assignPrimaryToken": + privilege = "SeAssignPrimaryTokenPrivilege"; + break; + case "audit": + privilege = "SeAuditPrivilege"; + break; + case "backup": + privilege = "SeBackupPrivilege"; + break; + case "changeNotify": + privilege = "SeChangeNotifyPrivilege"; + break; + case "createGlobal": + privilege = "SeCreateGlobalPrivilege"; + break; + case "createPagefile": + privilege = "SeCreatePagefilePrivilege"; + break; + case "createPermanent": + privilege = "SeCreatePermanentPrivilege"; + break; + case "createSymbolicLink": + privilege = "SeCreateSymbolicLinkPrivilege"; + break; + case "createToken": + privilege = "SeCreateTokenPrivilege"; + break; + case "debug": + privilege = "SeDebugPrivilege"; + break; + case "enableDelegation": + privilege = "SeEnableDelegationPrivilege"; + break; + case "impersonate": + privilege = "SeImpersonatePrivilege"; + break; + case "increaseBasePriority": + privilege = "SeIncreaseBasePriorityPrivilege"; + break; + case "increaseQuota": + privilege = "SeIncreaseQuotaPrivilege"; + break; + case "increaseWorkingSet": + privilege = "SeIncreaseWorkingSetPrivilege"; + break; + case "loadDriver": + privilege = "SeLoadDriverPrivilege"; + break; + case "lockMemory": + privilege = "SeLockMemoryPrivilege"; + break; + case "machineAccount": + privilege = "SeMachineAccountPrivilege"; + break; + case "manageVolume": + privilege = "SeManageVolumePrivilege"; + break; + case "profileSingleProcess": + privilege = "SeProfileSingleProcessPrivilege"; + break; + case "relabel": + privilege = "SeRelabelPrivilege"; + break; + case "remoteShutdown": + privilege = "SeRemoteShutdownPrivilege"; + break; + case "restore": + privilege = "SeRestorePrivilege"; + break; + case "security": + privilege = "SeSecurityPrivilege"; + break; + case "shutdown": + privilege = "SeShutdownPrivilege"; + break; + case "syncAgent": + privilege = "SeSyncAgentPrivilege"; + break; + case "systemEnvironment": + privilege = "SeSystemEnvironmentPrivilege"; + break; + case "systemProfile": + privilege = "SeSystemProfilePrivilege"; + break; + case "systemTime": + case "modifySystemTime": + privilege = "SeSystemtimePrivilege"; + break; + case "takeOwnership": + privilege = "SeTakeOwnershipPrivilege"; + break; + case "tcb": + case "trustedComputerBase": + privilege = "SeTcbPrivilege"; + break; + case "timeZone": + case "modifyTimeZone": + privilege = "SeTimeZonePrivilege"; + break; + case "trustedCredManAccess": + case "trustedCredentialManagerAccess": + privilege = "SeTrustedCredManAccessPrivilege"; + break; + case "undock": + privilege = "SeUndockPrivilege"; + break; + case "unsolicitedInput": + privilege = "SeUnsolicitedInputPrivilege"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (privilege == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + this.Core.ParseForExtensionElements(node); + + return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); + } + + /// + /// Parses a service config failure actions element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + int? resetPeriod = null; + string rebootMessage = null; + string command = null; + string actions = null; + string actionsDelays = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RebootMessage": + rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ResetPeriod": + resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfigFailureActions actions. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Failure": + string action = null; + string delay = null; + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + foreach (var childAttrib in child.Attributes()) + { + if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) + { + switch (childAttrib.Name.LocalName) + { + case "Action": + action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (action) + { + case "none": + action = "0"; + break; + case "restartComputer": + action = "2"; + break; + case "restartService": + action = "1"; + break; + case "runCommand": + action = "3"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "Delay": + delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + default: + this.Core.UnexpectedAttribute(child, childAttrib); + break; + } + } + } + + if (String.IsNullOrEmpty(action)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); + } + + if (String.IsNullOrEmpty(delay)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); + } + + if (!String.IsNullOrEmpty(actions)) + { + actions = String.Concat(actions, "[~]"); + } + actions = String.Concat(actions, action); + + if (!String.IsNullOrEmpty(actionsDelays)) + { + actionsDelays = String.Concat(actionsDelays, "[~]"); + } + actionsDelays = String.Concat(actionsDelays, delay); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ResetPeriod = resetPeriod, + RebootMessage = rebootMessage, + Command = command, + Actions = actions, + DelayActions = actionsDelays, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a service control element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseServiceControlElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string arguments = null; + Identifier id = null; + string name = null; + var installRemove = false; + var uninstallRemove = false; + var installStart = false; + var uninstallStart = false; + var installStop = false; + var uninstallStop = false; + bool? wait = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Remove": + var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (removeValue) + { + case "install": + installRemove = true; + break; + case "uninstall": + uninstallRemove = true; + break; + case "both": + installRemove = true; + uninstallRemove = true; + break; + case "": + break; + } + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "install": + installStart = true; + break; + case "uninstall": + uninstallStart = true; + break; + case "both": + installStart = true; + uninstallStart = true; + break; + case "": + break; + } + break; + case "Stop": + var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (stopValue) + { + case "install": + installStop = true; + break; + case "uninstall": + uninstallStop = true; + break; + case "both": + installStop = true; + uninstallStop = true; + break; + case "": + break; + } + break; + case "Wait": + wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + // get the ServiceControl arguments + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ServiceArgument": + arguments = this.ParseServiceArgument(child, arguments); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) + { + Name = name, + InstallRemove = installRemove, + UninstallRemove = uninstallRemove, + InstallStart = installStart, + UninstallStart = uninstallStart, + InstallStop = installStop, + UninstallStop = uninstallStop, + Arguments = arguments, + Wait = wait, + ComponentRef = componentId + }); + } + } + + private string ParseServiceArgument(XElement node, string arguments) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string argument = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (argument == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); + } + + /// + /// Parses a service dependency element. + /// + /// Element to parse. + /// Parsed sevice dependency name. + private string ParseServiceDependencyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string dependency = null; + var group = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Group": + group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return group ? String.Concat("+", dependency) : dependency; + } + + /// + /// Parses a service install element. + /// + /// Element to parse. + /// Identifier of parent component. + /// + private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string account = null; + string arguments = null; + string dependencies = null; + string description = null; + string displayName = null; + var eraseDescription = false; + string loadOrderGroup = null; + string name = null; + string password = null; + + var serviceType = ServiceType.OwnProcess; + var startType = ServiceStartType.Demand; + var errorControl = ServiceErrorControl.Normal; + var interactive = false; + var vital = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Account": + account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EraseDescription": + eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ErrorControl": + var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (errorControlValue) + { + case "ignore": + errorControl = ServiceErrorControl.Ignore; + break; + case "normal": + errorControl = ServiceErrorControl.Normal; + break; + case "critical": + errorControl = ServiceErrorControl.Critical; + break; + case "": // error case handled by GetAttributeValue() + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); + break; + } + break; + case "Interactive": + interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LoadOrderGroup": + loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Password": + password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "auto": + startType = ServiceStartType.Auto; + break; + case "demand": + startType = ServiceStartType.Demand; + break; + case "disabled": + startType = ServiceStartType.Disabled; + break; + case "boot": + case "system": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); + break; + } + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "ownProcess": + serviceType = ServiceType.OwnProcess; + break; + case "shareProcess": + serviceType = ServiceType.ShareProcess; + break; + case "kernelDriver": + case "systemDriver": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); + break; + } + break; + case "Vital": + vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (0 == startType) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); + } + + if (eraseDescription) + { + description = "[~]"; + } + + // get the ServiceInstall dependencies and config + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); + break; + case "ServiceConfig": + this.ParseServiceConfigElement(child, componentId, name); + break; + case "ServiceConfigFailureActions": + this.ParseServiceConfigFailureActionsElement(child, componentId, name); + break; + case "ServiceDependency": + dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (null != dependencies) + { + dependencies = String.Concat(dependencies, "[~]"); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) + { + Name = name, + DisplayName = displayName, + ServiceType = serviceType, + StartType = startType, + ErrorControl = errorControl, + LoadOrderGroup = loadOrderGroup, + Dependencies = dependencies, + StartName = account, + Password = password, + Arguments = arguments, + ComponentRef = componentId, + Description = description, + Interactive = interactive, + Vital = vital + }); + } + } + + /// + /// Parses a SetDirectory element. + /// + /// Element to parse. + private void ParseSetDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Directory, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, afterAction: "CostInitialize"); + } + } + } + + /// + /// Parses a SetProperty element. + /// + /// Element to parse. + private void ParseSetPropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + string afterAction = null; + string beforeAction = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Before": + beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); + } + else if (null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); + } + + this.Core.ParseForExtensionElements(node); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); + } + + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Property, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value, + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, beforeAction, afterAction); + } + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPFileElement(XElement node, string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) + { + FileRef = id, + SFPCatalogRef = parentSFPCatalog + }); + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string parentName = null; + string dependency = null; + string name = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Dependency": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + parentSFPCatalog = name; + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SFPCatalog": + this.ParseSFPCatalogElement(child, ref parentName); + if (null != dependency && parentName == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + dependency = parentName; + break; + case "SFPFile": + this.ParseSFPFileElement(child, name); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) + { + SFPCatalog = name, + Catalog = sourceFile, + Dependency = dependency + }); + } + } + + /// + /// Parses a shortcut element. + /// + /// Element to parse. + /// Identifer for parent component. + /// Local name of parent element. + /// Default identifier of parent (which is usually the target). + /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). + private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var advertise = false; + string arguments = null; + string description = null; + string descriptionResourceDll = null; + int? descriptionResourceId = null; + string directoryId = null; + string subdirectory = null; + string displayResourceDll = null; + int? displayResourceId = null; + int? hotkey = null; + string icon = null; + int? iconIndex = null; + string name = null; + string shortName = null; + ShortcutShowType? show = null; + string target = null; + string workingDirectoryId = null; + string workingSubdirectory = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Advertise": + advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceDll": + descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceId": + descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "DisplayResourceDll": + displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayResourceId": + displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Hotkey": + hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Show": + var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (showValue) + { + case "normal": + show = ShortcutShowType.Normal; + break; + case "maximized": + show = ShortcutShowType.Maximized; + break; + case "minimized": + show = ShortcutShowType.Minimized; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); + break; + } + break; + case "Target": + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "WorkingDirectory": + workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId); + break; + case "WorkingSubdirectory": + workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (advertise && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); + } + + if (null == directoryId) + { + if ("Component" == parentElementLocalName) + { + directoryId = defaultTarget; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); + } + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + if (null != descriptionResourceDll) + { + if (!descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); + } + } + else + { + if (descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); + } + } + + if (null != displayResourceDll) + { + if (!displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); + } + } + else + { + if (displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory"); + + if ("Component" != parentElementLocalName && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Icon": + icon = this.ParseIconElement(child); + break; + case "ShortcutProperty": + this.ParseShortcutPropertyElement(child, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (advertise) + { + if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) + { + this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); + } + + target = Guid.Empty.ToString("B"); + } + else if (null != target) + { + } + else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) + { + target = "[" + defaultTarget + "]"; + } + else if ("File" == parentElementLocalName) + { + target = "[#" + defaultTarget + "]"; + } + + this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) + { + DirectoryRef = directoryId, + Name = name, + ShortName = shortName, + ComponentRef = componentId, + Target = target, + Arguments = arguments, + Description = description, + Hotkey = hotkey, + IconRef = icon, + IconIndex = iconIndex, + Show = show, + WorkingDirectory = workingDirectoryId, + DisplayResourceDll = displayResourceDll, + DisplayResourceId = displayResourceId, + DescriptionResourceDll = descriptionResourceDll, + DescriptionResourceId = descriptionResourceId, + }); + } + } + + /// + /// Parses a shortcut property element. + /// + /// Element to parse. + /// + private void ParseShortcutPropertyElement(XElement node, string shortcutId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(key)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + else if (null == id) + { + id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); + } + + if (String.IsNullOrEmpty(value)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) + { + ShortcutRef = shortcutId, + PropertyKey = key, + PropVariantValue = value + }); + } + } + + /// + /// Parses a typelib element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of file that acts as typelib server. + /// true if the component is 64-bit. + private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var advertise = YesNoType.NotSet; + var cost = CompilerConstants.IntegerNotSet; + string description = null; + var flags = 0; + string helpDirectoryId = null; + string helpSubdirectory = null; + var language = CompilerConstants.IntegerNotSet; + var majorVersion = CompilerConstants.IntegerNotSet; + var minorVersion = CompilerConstants.IntegerNotSet; + var resourceId = CompilerConstants.LongNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Advertise": + advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Control": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 2; + } + break; + case "Cost": + cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HasDiskImage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 8; + } + break; + case "HelpDirectory": + helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId); + break; + case "HelpSubdirectory": + helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 4; + } + break; + case "Language": + language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "MajorVersion": + majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); + break; + case "MinorVersion": + minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + break; + case "ResourceId": + resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); + break; + case "Restricted": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (CompilerConstants.IntegerNotSet == language) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + language = CompilerConstants.IllegalInteger; + } + + helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory"); + + // build up the typelib version string for the registry if the major or minor version was specified + string registryVersion = null; + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + if (CompilerConstants.IntegerNotSet != majorVersion) + { + registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); + } + else + { + registryVersion = "0"; + } + + if (CompilerConstants.IntegerNotSet != minorVersion) + { + registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); + } + else + { + registryVersion = String.Concat(registryVersion, ".0"); + } + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); + break; + case "Interface": + this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (YesNoType.Yes == advertise) + { + if (CompilerConstants.LongNotSet != resourceId) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); + } + + if (0 != flags) + { + if (0x1 == (flags & 0x1)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); + } + + if (0x2 == (flags & 0x2)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); + } + + if (0x4 == (flags & 0x4)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); + } + + if (0x8 == (flags & 0x8)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) + { + LibId = id, + Language = language, + ComponentRef = componentId, + Description = description, + DirectoryRef = helpDirectoryId, + FeatureRef = Guid.Empty.ToString("B") + }); + + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); + } + + if (CompilerConstants.IntegerNotSet != cost) + { + symbol.Cost = cost; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); + } + + if (null == fileServer) + { + this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); + } + + if (null == registryVersion) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); + } + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] + var path = String.Concat("[#", fileServer, "]"); + if (CompilerConstants.LongNotSet != resourceId) + { + path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); + + if (null != helpDirectoryId) + { + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId); + } + } + } + + /// + /// Parses an upgrade element. + /// + /// Element to parse. + private void ParseUpgradeElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + // process the UpgradeVersion children here + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + switch (child.Name.LocalName) + { + case "Property": + this.ParsePropertyElement(child); + this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); + break; + case "UpgradeVersion": + this.ParseUpgradeVersionElement(child, id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // No rows created here. All row creation is done in ParseUpgradeVersionElement. + } + + /// + /// Parse upgrade version element. + /// + /// Element to parse. + /// Upgrade code. + private void ParseUpgradeVersionElement(XElement node, string upgradeId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string actionProperty = null; + string language = null; + string maximum = null; + string minimum = null; + var excludeLanguages = false; + var ignoreFailures = false; + var includeMax = false; + var includeMin = true; + var migrateFeatures = false; + var onlyDetect = false; + string removeFeatures = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludeLanguages": + excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IgnoreRemoveFailure": + ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMaximum": + includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": // this is "yes" by default + includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Language": + language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "MigrateFeatures": + migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnlyDetect": + onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Property": + actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "RemoveFeatures": + removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == actionProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) + { + this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); + } + + if (null == minimum && null == maximum) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeId, + VersionMin = minimum, + VersionMax = maximum, + Language = language, + ExcludeLanguages = excludeLanguages, + IgnoreRemoveFailures = ignoreFailures, + VersionMaxInclusive = includeMax, + VersionMinInclusive = includeMin, + MigrateFeatures = migrateFeatures, + OnlyDetect = onlyDetect, + Remove = removeFeatures, + ActionProperty = actionProperty + }); + + // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence + // if at least one row in Upgrade table lacks the OnlyDetect attribute. + if (!onlyDetect) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); + } + } + } + + /// + /// Parses a verb element. + /// + /// Element to parse. + /// Extension verb is releated to. + /// Optional progId for extension. + /// Identifier for parent component. + /// Flag if verb is advertised. + private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string argument = null; + string command = null; + var sequence = CompilerConstants.IntegerNotSet; + string targetFile = null; + string targetProperty = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Argument": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "TargetFile": + targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); + break; + case "TargetProperty": + targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null != targetFile && null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); + } + + this.Core.ParseForExtensionElements(node); + + if (YesNoType.Yes == advertise) + { + if (null != targetFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); + } + + if (null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) + { + ExtensionRef = extension, + Verb = id, + Command = command, + Argument = argument, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != sequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); + } + + if (null == targetFile && null == targetProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); + } + + string target = null; + if (null != targetFile) + { + target = String.Concat("\"[#", targetFile, "]\""); + } + else if (null != targetProperty) + { + target = String.Concat("\"[", targetProperty, "]\""); + } + + if (null != argument) + { + target = String.Concat(target, " ", argument); + } + + var prefix = progId ?? String.Concat(".", extension); + + if (null != command) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); + } + } + + /// + /// Parses a WixVariable element. + /// + /// Element to parse. + private void ParseWixVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var overridable = false; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Overridable": + overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) + { + Value = value, + Overridable = overridable + }); + } + } + + private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + switch (compressionLevel) + { + case "high": + return CompressionLevel.High; + case "low": + return CompressionLevel.Low; + case "medium": + return CompressionLevel.Medium; + case "mszip": + return CompressionLevel.Mszip; + case "none": + return CompressionLevel.None; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); + break; + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Patch.cs b/src/wix/WixToolset.Core/Compiler_Patch.cs new file mode 100644 index 00000000..c9cae183 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Patch.cs @@ -0,0 +1,657 @@ +// 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.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses an patch element. + /// + /// The element to parse. + private void ParsePatchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string patchId = null; + string codepage = null; + ////bool versionMismatches = false; + ////bool productMismatches = false; + var allowRemoval = false; + string classification = null; + string clientPatchId = null; + string description = null; + string displayName = null; + string comments = null; + string manufacturer = null; + var minorUpdateTargetRTM = YesNoType.NotSet; + string moreInfoUrl = null; + var optimizeCA = CompilerConstants.IntegerNotSet; + var optimizedInstallMode = YesNoType.NotSet; + string targetProductName = null; + // string replaceGuids = String.Empty; + var apiPatchingSymbolFlags = 0; + var optimizePatchSizeForLargeFiles = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + case "AllowMajorVersionMismatches": + ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "AllowProductCodeMismatches": + ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "AllowRemoval": + allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "Classification": + classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ClientPatchId": + clientPatchId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Comments": + comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MinorUpdateTargetRTM": + minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "MoreInfoURL": + moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "OptimizedInstallMode": + optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TargetProductName": + targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ApiPatchingSymbolNoImagehlpFlag": + apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoImagehlp : 0; + break; + case "ApiPatchingSymbolNoFailuresFlag": + apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoFailures : 0; + break; + case "ApiPatchingSymbolUndecoratedTooFlag": + apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolUndecoratedToo : 0; + break; + case "OptimizePatchSizeForLargeFiles": + optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (patchId == null || patchId == "*") + { + // auto-generate at compile time, since this value gets dispersed to several locations + patchId = Common.GenerateGuid(); + } + this.activeName = patchId; + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + if (null == classification) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); + } + if (null == clientPatchId) + { + clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); + } + if (null == description) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); + } + if (null == displayName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); + } + if (null == manufacturer) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); + } + + this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PatchInformation": + this.ParsePatchInformationElement(child); + break; + case "Media": + this.ParseMediaElement(child, patchId); + break; + case "OptimizeCustomActions": + optimizeCA = this.ParseOptimizeCustomActionsElement(child); + break; + case "PatchFamily": + this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchFamilyRef": + this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchFamilyGroup": + this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchFamilyGroupRef": + this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchProperty": + this.ParsePatchPropertyElement(child, true); + break; + case "TargetProductCodes": + this.ParseTargetProductCodesElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) + { + Codepage = codepage, + ClientPatchId = clientPatchId, + OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, + ApiPatchingSymbolFlags = apiPatchingSymbolFlags, + }); + + if (allowRemoval) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0"); + } + + if (null != classification) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification); + } + + // always generate the CreationTimeUTC + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture)); + } + + if (null != description) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description); + } + + if (null != displayName) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); + } + + if (null != manufacturer) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer); + } + + if (YesNoType.NotSet != minorUpdateTargetRTM) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"); + } + + if (null != moreInfoUrl) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); + } + + if (CompilerConstants.IntegerNotSet != optimizeCA) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); + } + + if (YesNoType.NotSet != optimizedInstallMode) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); + } + + if (null != targetProductName) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); + } + + if (null != comments) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments); + } + } + // TODO: do something with versionMismatches and productMismatches + } + + /// + /// Parses the OptimizeCustomActions element. + /// + /// Element to parse. + /// The combined integer value for callers to store as appropriate. + private int ParseOptimizeCustomActionsElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var optimizeCA = OptimizeCAFlags.None; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "SkipAssignment": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + optimizeCA |= OptimizeCAFlags.SkipAssignment; + } + break; + case "SkipImmediate": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + optimizeCA |= OptimizeCAFlags.SkipImmediate; + } + break; + case "SkipDeferred": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + optimizeCA |= OptimizeCAFlags.SkipDeferred; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + return (int)optimizeCA; + } + + /// + /// Parses a PatchFamily element. + /// + /// The element to parse. + /// + /// + private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string productCode = null; + string version = null; + var attributes = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Supersede": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 0x1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + if (String.IsNullOrEmpty(version)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidProductVersion(version)) + { + this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); + } + + // find unexpected child elements + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "All": + this.ParseAllElement(child); + break; + case "BinaryRef": + this.ParsePatchChildRefElement(child, "Binary"); + break; + case "ComponentRef": + this.ParsePatchChildRefElement(child, "Component"); + break; + case "CustomActionRef": + this.ParsePatchChildRefElement(child, "CustomAction"); + break; + case "DirectoryRef": + this.ParsePatchChildRefElement(child, "Directory"); + break; + case "DigitalCertificateRef": + this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); + break; + case "FeatureRef": + this.ParsePatchChildRefElement(child, "Feature"); + break; + case "IconRef": + this.ParsePatchChildRefElement(child, "Icon"); + break; + case "PropertyRef": + this.ParsePatchChildRefElement(child, "Property"); + break; + case "SoftwareTagRef": + this.ParseTagRefElement(child); + break; + case "UIRef": + this.ParsePatchChildRefElement(child, "WixUI"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiPatchSequenceSymbol(sourceLineNumbers) + { + PatchFamily = id.Id, + ProductCode = productCode, + Sequence = version, + Attributes = attributes + }); + + if (ComplexReferenceParentType.Unknown != parentType) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); + } + } + } + + /// + /// Parses a PatchFamilyGroup element. + /// + /// Element to parse. + /// + /// + private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PatchFamily": + this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); + break; + case "PatchFamilyRef": + this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); + break; + case "PatchFamilyGroupRef": + this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchFamilyGroupSymbol(sourceLineNumbers, id)); + + //Add this PatchFamilyGroup and its parent in WixGroup. + this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id); + } + } + + /// + /// Parses a PatchFamilyGroup reference element. + /// + /// Element to parse. + /// The type of parent. + /// Identifier of parent element. + private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixPatchFamilyGroup, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true); + } + } + + /// + /// Parses a TargetProductCodes element. + /// + /// The element to parse. + private void ParseTargetProductCodesElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var replace = false; + var targetProductCodes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Replace": + replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "TargetProductCode": + var id = this.ParseTargetProductCodeElement(child); + if (0 == String.CompareOrdinal("*", id)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); + } + else + { + targetProductCodes.Add(id); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + // By default, target ProductCodes should be added. + if (!replace) + { + this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) + { + ProductCode = "*" + }); + } + + foreach (var targetProductCode in targetProductCodes) + { + this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) + { + ProductCode = targetProductCode + }); + } + } + } + + private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) + { + this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) + { + Company = company, + Property = property, + Value = value + }); + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_PatchCreation.cs b/src/wix/WixToolset.Core/Compiler_PatchCreation.cs new file mode 100644 index 00000000..81ae4121 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_PatchCreation.cs @@ -0,0 +1,1265 @@ +// 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.Collections.Generic; + using System.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a patch creation element. + /// + /// The element to parse. + private void ParsePatchCreationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var clean = true; // Default is to clean + var codepage = 0; + string outputPath = null; + var productMismatches = false; + var replaceGuids = String.Empty; + string sourceList = null; + string symbolFlags = null; + var targetProducts = String.Empty; + var versionMismatches = false; + var wholeFiles = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + this.activeName = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "AllowMajorVersionMismatches": + versionMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "AllowProductCodeMismatches": + productMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "CleanWorkingFolder": + clean = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Codepage": + codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + break; + case "OutputPath": + outputPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SourceList": + sourceList = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SymbolFlags": + symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, UInt32.MaxValue)); + break; + case "WholeFilesOnly": + wholeFiles = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Family": + this.ParseFamilyElement(child); + break; + case "PatchInformation": + this.ParsePatchInformationElement(child); + break; + case "PatchMetadata": + this.ParsePatchMetadataElement(child); + break; + case "PatchProperty": + this.ParsePatchPropertyElement(child, false); + break; + case "PatchSequence": + this.ParsePatchSequenceElement(child); + break; + case "ReplacePatch": + replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child)); + break; + case "TargetProductCode": + var targetProduct = this.ParseTargetProductCodeElement(child); + if (0 < targetProducts.Length) + { + targetProducts = String.Concat(targetProducts, ";"); + } + targetProducts = String.Concat(targetProducts, targetProduct); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + this.AddPrivateProperty(sourceLineNumbers, "PatchGUID", this.activeName); + this.AddPrivateProperty(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0"); + this.AddPrivateProperty(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0"); + this.AddPrivateProperty(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1"); + this.AddPrivateProperty(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0"); + + if (null != symbolFlags) + { + this.AddPrivateProperty(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags); + } + + if (0 < replaceGuids.Length) + { + this.AddPrivateProperty(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids); + } + + if (0 < targetProducts.Length) + { + this.AddPrivateProperty(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts); + } + + if (null != outputPath) + { + this.AddPrivateProperty(sourceLineNumbers, "PatchOutputPath", outputPath); + } + + if (null != sourceList) + { + this.AddPrivateProperty(sourceLineNumbers, "PatchSourceList", sourceList); + } + } + + /// + /// Parses a family element. + /// + /// The element to parse. + private void ParseFamilyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var diskId = CompilerConstants.IntegerNotSet; + string diskPrompt = null; + string mediaSrcProp = null; + string name = null; + var sequenceStart = CompilerConstants.IntegerNotSet; + string volumeLabel = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "DiskPrompt": + diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MediaSrcProp": + mediaSrcProp = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SequenceStart": + sequenceStart = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); + break; + case "VolumeLabel": + volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (0 < name.Length) + { + if (8 < name.Length) // check the length + { + this.Core.Write(ErrorMessages.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length)); + } + else // check for illegal characters + { + foreach (var character in name) + { + if (!Char.IsLetterOrDigit(character) && '_' != character) + { + this.Core.Write(ErrorMessages.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name)); + } + } + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "UpgradeImage": + this.ParseUpgradeImageElement(child, name); + break; + case "ExternalFile": + this.ParseExternalFileElement(child, name); + break; + case "ProtectFile": + this.ParseProtectFileElement(child, name); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ImageFamiliesSymbol(sourceLineNumbers) + { + Family = name, + MediaSrcPropName = mediaSrcProp, + DiskPrompt = diskPrompt, + VolumeLabel = volumeLabel + }); + + if (CompilerConstants.IntegerNotSet != diskId) + { + symbol.MediaDiskId = diskId; + } + + if (CompilerConstants.IntegerNotSet != sequenceStart) + { + symbol.FileSequenceStart = sequenceStart; + } + } + } + + /// + /// Parses an upgrade image element. + /// + /// The element to parse. + /// The family for this element. + private void ParseUpgradeImageElement(XElement node, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string sourceFile = null; + string sourcePatch = null; + var symbols = new List(); + string upgrade = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + upgrade = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (13 < upgrade.Length) + { + this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13)); + } + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SourcePatch": + sourcePatch = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == upgrade) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SymbolPath": + symbols.Add(this.ParseSymbolPathElement(child)); + break; + case "TargetImage": + this.ParseTargetImageElement(child, upgrade, family); + break; + case "UpgradeFile": + this.ParseUpgradeFileElement(child, upgrade); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradedImagesSymbol(sourceLineNumbers) + { + Upgraded = upgrade, + MsiPath = sourceFile, + PatchMsiPath = sourcePatch, + SymbolPaths = String.Join(";", symbols), + Family = family + }); + } + } + + /// + /// Parses an upgrade file element. + /// + /// The element to parse. + /// The upgrade key for this element. + private void ParseUpgradeFileElement(XElement node, string upgrade) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var allowIgnoreOnError = false; + string file = null; + var ignore = false; + var symbols = new List(); + var wholeFile = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AllowIgnoreOnError": + allowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "File": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Ignore": + ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "WholeFile": + wholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SymbolPath": + symbols.Add(this.ParseSymbolPathElement(child)); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (ignore) + { + this.Core.AddSymbol(new UpgradedFilesToIgnoreSymbol(sourceLineNumbers) + { + Upgraded = upgrade, + FTK = file + }); + } + else + { + this.Core.AddSymbol(new UpgradedFilesOptionalDataSymbol(sourceLineNumbers) + { + Upgraded = upgrade, + FTK = file, + SymbolPaths = String.Join(";", symbols), + AllowIgnoreOnPatchError = allowIgnoreOnError, + IncludeWholeFile = wholeFile + }); + } + } + } + + /// + /// Parses a target image element. + /// + /// The element to parse. + /// The upgrade key for this element. + /// The family key for this element. + private void ParseTargetImageElement(XElement node, string upgrade, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var ignore = false; + var order = CompilerConstants.IntegerNotSet; + string sourceFile = null; + string symbols = null; + string target = null; + string validation = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (target.Length > 13) + { + this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13)); + } + break; + case "IgnoreMissingFiles": + ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Order": + order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Validation": + validation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == target) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + if (CompilerConstants.IntegerNotSet == order) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SymbolPath": + if (null != symbols) + { + symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child)); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "TargetFile": + this.ParseTargetFileElement(child, target, family); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new TargetImagesSymbol(sourceLineNumbers) + { + Target = target, + MsiPath = sourceFile, + SymbolPaths = symbols, + Upgraded = upgrade, + Order = order, + ProductValidateFlags = validation, + IgnoreMissingSrcFiles = ignore + }); + } + } + + /// + /// Parses an upgrade file element. + /// + /// The element to parse. + /// The upgrade key for this element. + /// The family key for this element. + private void ParseTargetFileElement(XElement node, string target, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string file = null; + string ignoreLengths = null; + string ignoreOffsets = null; + string protectLengths = null; + string protectOffsets = null; + string symbols = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "SymbolPath": + symbols = this.ParseSymbolPathElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new TargetFilesOptionalDataSymbol(sourceLineNumbers) + { + Target = target, + FTK = file, + SymbolPaths = symbols, + IgnoreOffsets = ignoreOffsets, + IgnoreLengths = ignoreLengths, + }); + + if (null != protectOffsets) + { + symbol.RetainOffsets = protectOffsets; + + this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + RetainOffsets = protectOffsets, + RetainLengths = protectLengths, + }); + } + } + } + + /// + /// Parses an external file element. + /// + /// The element to parse. + /// The family for this element. + private void ParseExternalFileElement(XElement node, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string file = null; + string ignoreLengths = null; + string ignoreOffsets = null; + var order = CompilerConstants.IntegerNotSet; + string protectLengths = null; + string protectOffsets = null; + string source = null; + string symbols = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "File": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Order": + order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); + } + + if (null == source) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source")); + } + + if (CompilerConstants.IntegerNotSet == order) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "SymbolPath": + symbols = this.ParseSymbolPathElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ExternalFilesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + FilePath = source, + SymbolPaths = symbols, + IgnoreOffsets = ignoreOffsets, + IgnoreLengths = ignoreLengths, + }); + + if (null != protectOffsets) + { + symbol.RetainOffsets = protectOffsets; + } + + if (CompilerConstants.IntegerNotSet != order) + { + symbol.Order = order; + } + + if (null != protectOffsets) + { + this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + RetainOffsets = protectOffsets, + RetainLengths = protectLengths, + }); + } + } + } + + /// + /// Parses a protect file element. + /// + /// The element to parse. + /// The family for this element. + private void ParseProtectFileElement(XElement node, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string file = null; + string protectLengths = null; + string protectOffsets = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "File": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == protectOffsets || null == protectLengths) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + RetainOffsets = protectOffsets, + RetainLengths = protectLengths + }); + } + } + + /// + /// Parses a range element (ProtectRange, IgnoreRange, etc). + /// + /// The element to parse. + /// Reference to the offsets string. + /// Reference to the lengths string. + private void ParseRangeElement(XElement node, ref string offsets, ref string lengths) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string length = null; + string offset = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Length": + length = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Offset": + offset = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == length) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length")); + } + + if (null == offset) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); + } + + this.Core.ParseForExtensionElements(node); + + if (null != lengths) + { + lengths = String.Concat(lengths, ",", length); + } + else + { + lengths = length; + } + + if (null != offsets) + { + offsets = String.Concat(offsets, ",", offset); + } + else + { + offsets = offset; + } + } + + /// + /// Parses a patch metadata element. + /// + /// Element to parse. + private void ParsePatchMetadataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var allowRemoval = YesNoType.NotSet; + string classification = null; + string creationTimeUtc = null; + string description = null; + string displayName = null; + string manufacturerName = null; + string minorUpdateTargetRTM = null; + string moreInfoUrl = null; + var optimizeCA = CompilerConstants.IntegerNotSet; + var optimizedInstallMode = YesNoType.NotSet; + string targetProductName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AllowRemoval": + allowRemoval = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Classification": + classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "CreationTimeUTC": + creationTimeUtc = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ManufacturerName": + manufacturerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MinorUpdateTargetRTM": + minorUpdateTargetRTM = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MoreInfoURL": + moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "OptimizedInstallMode": + optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TargetProductName": + targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (YesNoType.NotSet == allowRemoval) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval")); + } + + if (null == classification) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); + } + + if (null == description) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); + } + + if (null == displayName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); + } + + if (null == manufacturerName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName")); + } + + if (null == moreInfoUrl) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL")); + } + + if (null == targetProductName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "CustomProperty": + this.ParseCustomPropertyElement(child); + break; + case "OptimizeCustomActions": + optimizeCA = this.ParseOptimizeCustomActionsElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (YesNoType.NotSet != allowRemoval) + { + this.AddPatchMetadata(sourceLineNumbers, null, "AllowRemoval", YesNoType.Yes == allowRemoval ? "1" : "0"); + } + + if (null != classification) + { + this.AddPatchMetadata(sourceLineNumbers, null, "Classification", classification); + } + + if (null != creationTimeUtc) + { + this.AddPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", creationTimeUtc); + } + + if (null != description) + { + this.AddPatchMetadata(sourceLineNumbers, null, "Description", description); + } + + if (null != displayName) + { + this.AddPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); + } + + if (null != manufacturerName) + { + this.AddPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturerName); + } + + if (null != minorUpdateTargetRTM) + { + this.AddPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", minorUpdateTargetRTM); + } + + if (null != moreInfoUrl) + { + this.AddPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); + } + + if (CompilerConstants.IntegerNotSet != optimizeCA) + { + this.AddPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); + } + + if (YesNoType.NotSet != optimizedInstallMode) + { + this.AddPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); + } + + if (null != targetProductName) + { + this.AddPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); + } + } + } + + /// + /// Parses a custom property element for the PatchMetadata table. + /// + /// Element to parse. + private void ParseCustomPropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string company = null; + string property = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Company": + company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Property": + property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == company) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.AddPatchMetadata(sourceLineNumbers, company, property, value); + } + } + + /// + /// Parses a patch sequence element. + /// + /// The element to parse. + private void ParsePatchSequenceElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string family = null; + string target = null; + string sequence = null; + var attributes = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "PatchFamily": + family = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ProductCode": + if (null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage")); + } + target = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Target": + if (null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode")); + } + this.Core.Write(WarningMessages.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TargetImage": + if (null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.TargetImages, target); + break; + case "Sequence": + sequence = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Supersede": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 0x1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == family) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new PatchSequenceSymbol(sourceLineNumbers) + { + PatchFamily = family, + Target = target, + Sequence = sequence, + Supersede = attributes, + }); + } + } + + private void AddPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) + { + this.Core.AddSymbol(new PatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) + { + Company = company, + Property = property, + Value = value, + }); + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Tag.cs b/src/wix/WixToolset.Core/Compiler_Tag.cs new file mode 100644 index 00000000..cf55c448 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Tag.cs @@ -0,0 +1,315 @@ +// 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.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a Tag element for Software Id Tag registration under a Bundle element. + /// + /// The element to parse. + private void ParseBundleTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string regid = null; + string installPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + case "Bitness": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); + break; + case "InstallPath": + installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (String.IsNullOrEmpty(installPath)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) + { + Filename = String.Concat(name, ".swidtag"), + Regid = regid, + Name = name, + InstallPath = installPath + }); + } + } + + /// + /// Parses a Tag element for Software Id Tag registration under a Package element. + /// + /// The element to parse. + private void ParsePackageTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + string regid = null; + string feature = null; + string installDirectory = null; + var win64 = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallPath": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + return; + } + else if (id == null) + { + id = this.CreateTagId(regid); + } + + if (String.IsNullOrEmpty(installDirectory)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); + } + + if (!this.Core.EncounteredError) + { + var fileName = String.Concat(name, ".swidtag"); + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); + this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + Name = "swidtag", + ParentDirectoryRef = installDirectory, + ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" + }); + + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) + { + ComponentId = "*", + DirectoryRef = id.Id, + KeyPath = id.Id, + KeyPathType = ComponentKeyPathType.File, + Location = ComponentLocation.LocalOnly, + Win64 = win64 + }); + + this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + Name = fileName, + DiskId = 1, + Attributes = FileSymbolAttributes.ReadOnly, + }); + + if (!String.IsNullOrEmpty(feature)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + } + else + { + feature = "WixSwidTag"; + this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, feature)) + { + Title = "ISO/IEC 19770-2", + Level = 1, + InstallDefault = FeatureInstallDefault.Local, + Display = 0, + DisallowAdvertise = true, + DisallowAbsent = true, + }); + } + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); + + this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); + this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) + { + FileRef = id.Id, + Regid = regid, + Name = name + }); + } + } + + /// + /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. + /// + /// The element to parse. + private void ParseTagRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string regid = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (!this.Core.EncounteredError) + { + var id = this.CreateTagId(regid); + + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) + { + Table = SymbolDefinitions.Component.Name, + PrimaryKeys = id.Id + }); + } + } + + private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); + } +} diff --git a/src/wix/WixToolset.Core/Compiler_UI.cs b/src/wix/WixToolset.Core/Compiler_UI.cs new file mode 100644 index 00000000..d712ec91 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_UI.cs @@ -0,0 +1,1808 @@ +// 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.Collections; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + // NameToBit arrays + private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; + private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; + private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; + private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; + private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; + private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; + private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; + private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; + private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; + private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; + private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; + + /// + /// Parses UI elements. + /// + /// Element to parse. + private void ParseUIElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var embeddedUICount = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "BillboardAction": + this.ParseBillboardActionElement(child); + break; + case "ComboBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); + break; + case "Dialog": + this.ParseDialogElement(child); + break; + case "DialogRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Dialog); + break; + case "EmbeddedUI": + if (0 < embeddedUICount) // there can be only one embedded UI + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + } + this.ParseEmbeddedUIElement(child); + ++embeddedUICount; + break; + case "Error": + this.ParseErrorElement(child); + break; + case "ListBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); + break; + case "ListView": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); + break; + case "ProgressText": + this.ParseActionTextElement(child); + break; + case "Publish": + var order = 0; + this.ParsePublishElement(child, null, null, ref order); + break; + case "RadioButtonGroup": + var radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet); + if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers)); + } + break; + case "TextStyle": + this.ParseTextStyleElement(child); + break; + case "UIText": + this.ParseUITextElement(child); + break; + + // the following are available indentically under the UI and Product elements for document organization use only + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null != id && !this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixUISymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a list item element. + /// + /// Element to parse. + /// Type of symbol to create. + /// Identifier of property referred to by list item. + /// Relative order of list items. + private void ParseListItemElement(XElement node, SymbolDefinitionType symbolType, string property, ref int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string icon = null; + string text = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Icon": + if (SymbolDefinitionType.ListView == symbolType) + { + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, icon); + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView")); + } + break; + case "Text": + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + switch (symbolType) + { + case SymbolDefinitionType.ComboBox: + this.Core.AddSymbol(new ComboBoxSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + }); + break; + case SymbolDefinitionType.ListBox: + this.Core.AddSymbol(new ListBoxSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + }); + break; + case SymbolDefinitionType.ListView: + var symbol = this.Core.AddSymbol(new ListViewSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + }); + + if (null != icon) + { + symbol.BinaryRef = icon; + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolType)); + } + } + } + + /// + /// Parses a radio button element. + /// + /// Element to parse. + /// Identifier of property referred to by radio button. + /// Relative order of radio buttons. + /// Type of this radio button. + private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var type = RadioButtonType.NotSet; + string value = null; + string x = null; + string y = null; + string width = null; + string height = null; + string text = null; + string tooltip = null; + string help = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Bitmap": + if (RadioButtonType.NotSet != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text")); + } + text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); + type = RadioButtonType.Bitmap; + break; + case "Height": + height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Help": + help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Icon": + if (RadioButtonType.NotSet != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text")); + } + text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); + type = RadioButtonType.Icon; + break; + case "Text": + if (RadioButtonType.NotSet != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon")); + } + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + type = RadioButtonType.Text; + break; + case "ToolTip": + tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Width": + width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "X": + x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Y": + y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + if (null == x) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); + } + + if (null == y) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); + } + + if (null == width) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); + } + + if (null == height) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new RadioButtonSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + Help = (null != tooltip || null != help) ? String.Concat(tooltip, "|", help) : null + }); + + symbol.Set((int)RadioButtonSymbolFields.X, x); + symbol.Set((int)RadioButtonSymbolFields.Y, y); + symbol.Set((int)RadioButtonSymbolFields.Width, width); + symbol.Set((int)RadioButtonSymbolFields.Height, height); + } + + return type; + } + + /// + /// Parses a billboard element. + /// + /// Element to parse. + private void ParseBillboardActionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string action = null; + var order = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + action = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", action); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == action) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Billboard": + order = order + 1; + this.ParseBillboardElement(child, action, order); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a billboard element. + /// + /// Element to parse. + /// Action for the billboard. + /// Order of the billboard. + private void ParseBillboardElement(XElement node, string action, int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string feature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("bil", action, order.ToString(), feature); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Control": + // These are all thrown away. + ControlSymbol lastTabSymbol = null; + string firstControl = null; + string defaultControl = null; + string cancelControl = null; + + this.ParseControlElement(child, id.Id, SymbolDefinitionType.BBControl, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new BillboardSymbol(sourceLineNumbers, id) + { + FeatureRef = feature, + Action = action, + Ordering = order + }); + } + } + + /// + /// Parses a control group element. + /// + /// Element to parse. + /// Symbol type referred to by control group. + /// Expected child elements. + private void ParseControlGroupElement(XElement node, SymbolDefinitionType symbolType, string childTag) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var order = 0; + string property = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + if (childTag != child.Name.LocalName) + { + this.Core.UnexpectedElement(node, child); + } + + switch (child.Name.LocalName) + { + case "ListItem": + this.ParseListItemElement(child, symbolType, property, ref order); + break; + case "Property": + this.ParsePropertyElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + } + + /// + /// Parses a radio button control group element. + /// + /// Element to parse. + /// Property associated with this radio button group. + /// Specifies the current type of radio buttons in the group. + /// The current type of radio buttons in the group. + private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var order = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "RadioButton": + var type = this.ParseRadioButtonElement(child, property, ref order); + if (RadioButtonType.NotSet == groupType) + { + groupType = type; + } + else if (groupType != type) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.RadioButtonTypeInconsistent(childSourceLineNumbers)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + return groupType; + } + + /// + /// Parses an action text element. + /// + /// Element to parse. + private void ParseActionTextElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string action = null; + string message = null; + string template = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Template": + template = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == action) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ActionTextSymbol(sourceLineNumbers) + { + Action = action, + Description = message, + Template = template, + }); + } + } + + /// + /// Parses an ui text element. + /// + /// Element to parse. + private void ParseUITextElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string text = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Value": + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("txt", text); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UITextSymbol(sourceLineNumbers, id) + { + Text = text, + }); + } + } + + /// + /// Parses a text style element. + /// + /// Element to parse. + private void ParseTextStyleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + int? red = null; + int? green = null; + int? blue = null; + var bold = false; + var italic = false; + var strike = false; + var underline = false; + string faceName = null; + var size = "0"; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + + // RGB Values + case "Red": + var redColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + if (CompilerConstants.IllegalInteger != redColor) + { + red = redColor; + } + break; + case "Green": + var greenColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + if (CompilerConstants.IllegalInteger != greenColor) + { + green = greenColor; + } + break; + case "Blue": + var blueColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + if (CompilerConstants.IllegalInteger != blueColor) + { + blue = blueColor; + } + break; + + // Style values + case "Bold": + bold = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Italic": + italic = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Strike": + strike = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Underline": + underline = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + + // Font values + case "FaceName": + faceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Size": + size = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.CreateIdentifier("txs", faceName, size.ToString(), (red ?? 0).ToString(), (green ?? 0).ToString(), (blue ?? 0).ToString(), bold.ToString(), italic.ToString(), strike.ToString(), underline.ToString()); + } + + if (null == faceName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new TextStyleSymbol(sourceLineNumbers, id) + { + FaceName = faceName, + LocalizedSize = size, + Red = red, + Green = green, + Blue = blue, + Bold = bold, + Italic = italic, + Strike = strike, + Underline = underline, + }); + } + } + + /// + /// Parses a dialog element. + /// + /// Element to parse. + private void ParseDialogElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var hidden = false; + var modal = true; + var minimize = true; + var customPalette = false; + var errorDialog = false; + var keepModeless = false; + var height = 0; + string title = null; + var leftScroll = false; + var rightAligned = false; + var rightToLeft = false; + var systemModal = false; + var trackDiskSpace = false; + var width = 0; + var x = 50; + var y = 50; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Height": + height = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Title": + title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Width": + width = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "X": + x = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); + break; + case "Y": + y = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); + break; + case "CustomPalette": + customPalette = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ErrorDialog": + errorDialog = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "KeepModeless": + keepModeless = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LeftScroll": + leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Modeless": + modal = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "NoMinimize": + minimize = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightAligned": + rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightToLeft": + rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SystemModal": + systemModal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TrackDiskSpace": + trackDiskSpace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + ControlSymbol lastTabSymbol = null; + string cancelControl = null; + string defaultControl = null; + string firstControl = null; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Control": + this.ParseControlElement(child, id.Id, SymbolDefinitionType.Control, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null != lastTabSymbol && null != lastTabSymbol.Control) + { + if (firstControl != lastTabSymbol.Control) + { + lastTabSymbol.NextControlRef = firstControl; + } + } + + if (null == firstControl) + { + this.Core.Write(ErrorMessages.NoFirstControlSpecified(sourceLineNumbers, id.Id)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new DialogSymbol(sourceLineNumbers, id) + { + HCentering = x, + VCentering = y, + Width = width, + Height = height, + CustomPalette = customPalette, + ErrorDialog = errorDialog, + Visible = !hidden, + Modal = modal, + KeepModeless = keepModeless, + LeftScroll = leftScroll, + Minimize = minimize, + RightAligned = rightAligned, + RightToLeft = rightToLeft, + SystemModal = systemModal, + TrackDiskSpace = trackDiskSpace, + Title = title, + FirstControlRef = firstControl, + DefaultControlRef = defaultControl, + CancelControlRef = cancelControl, + }); + } + } + + /// + /// Parses a control element. + /// + /// Element to parse. + /// Identifier for parent dialog. + /// Table control belongs in. + /// Last control in the tab order. + /// Name of the first control in the tab order. + /// Name of the default control. + /// Name of the candle control. + private void ParseControlElement(XElement node, string dialog, SymbolDefinitionType symbolType, ref ControlSymbol lastTabSymbol, ref string firstControl, ref string defaultControl, ref string cancelControl) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier controlId = null; + var bits = new BitArray(32); + string checkBoxPropertyRef = null; + string checkboxValue = null; + string controlType = null; + var disabled = false; + string height = null; + string help = null; + var isCancel = false; + var isDefault = false; + var notTabbable = false; + string property = null; + var publishOrder = 0; + string sourceFile = null; + string text = null; + string tooltip = null; + var radioButtonsType = RadioButtonType.NotSet; + string width = null; + string x = null; + string y = null; + + string defaultCondition = null; + string enableCondition = null; + string disableCondition = null; + string hideCondition = null; + string showCondition = null; + + var hidden = false; + var sunken = false; + var indirect = false; + var integer = false; + var rightToLeft = false; + var rightAligned = false; + var leftScroll = false; + + // The rest of the method relies on the control's Type, so we have to get that first. + var typeAttribute = node.Attribute("Type"); + if (null == typeAttribute) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + else + { + controlType = this.Core.GetAttributeValue(sourceLineNumbers, typeAttribute); + } + + string[] specialAttributes; + switch (controlType) + { + case "Billboard": + specialAttributes = null; + notTabbable = true; + disabled = true; + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Billboard); + break; + case "Bitmap": + specialAttributes = BitmapControlAttributes; + notTabbable = true; + disabled = true; + break; + case "CheckBox": + specialAttributes = CheckboxControlAttributes; + break; + case "ComboBox": + specialAttributes = ComboboxControlAttributes; + break; + case "DirectoryCombo": + specialAttributes = VolumeControlAttributes; + break; + case "DirectoryList": + specialAttributes = null; + break; + case "Edit": + specialAttributes = EditControlAttributes; + break; + case "GroupBox": + specialAttributes = null; + notTabbable = true; + break; + case "Hyperlink": + specialAttributes = HyperlinkControlAttributes; + break; + case "Icon": + specialAttributes = IconControlAttributes; + notTabbable = true; + disabled = true; + break; + case "Line": + specialAttributes = null; + notTabbable = true; + disabled = true; + break; + case "ListBox": + specialAttributes = ListboxControlAttributes; + break; + case "ListView": + specialAttributes = ListviewControlAttributes; + break; + case "MaskedEdit": + specialAttributes = EditControlAttributes; + break; + case "PathEdit": + specialAttributes = EditControlAttributes; + break; + case "ProgressBar": + specialAttributes = ProgressControlAttributes; + notTabbable = true; + disabled = true; + break; + case "PushButton": + specialAttributes = ButtonControlAttributes; + break; + case "RadioButtonGroup": + specialAttributes = RadioControlAttributes; + break; + case "ScrollableText": + specialAttributes = null; + break; + case "SelectionTree": + specialAttributes = null; + break; + case "Text": + specialAttributes = TextControlAttributes; + notTabbable = true; + break; + case "VolumeCostList": + specialAttributes = VolumeControlAttributes; + notTabbable = true; + break; + case "VolumeSelectCombo": + specialAttributes = VolumeControlAttributes; + break; + default: + specialAttributes = null; + notTabbable = true; + break; + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + controlId = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Type": // already processed + break; + case "Cancel": + isCancel = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "CheckBoxPropertyRef": + checkBoxPropertyRef = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "CheckBoxValue": + checkboxValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Default": + isDefault = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "DefaultCondition": + defaultCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EnableCondition": + enableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisableCondition": + disableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HideCondition": + hideCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShowCondition": + showCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Height": + height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Help": + help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "IconSize": + var iconSizeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != specialAttributes) + { + switch (iconSizeValue) + { + case "16": + this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); + break; + case "32": + this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); + break; + case "48": + this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); + this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); + break; + } + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type")); + } + break; + case "Property": + property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TabSkip": + notTabbable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Text": + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ToolTip": + tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Width": + width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "X": + x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Y": + y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Disabled": + disabled = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Sunken": + sunken = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Indirect": + indirect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Integer": + integer = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightToLeft": + rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightAligned": + rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LeftScroll": + leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (null == specialAttributes || !this.Core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16)) + { + this.Core.UnexpectedAttribute(node, attrib); + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var attributes = this.Core.CreateIntegerFromBitArray(bits); + + //if (disabled) + //{ + // attributes |= WindowsInstallerConstants.MsidbControlAttributesEnabled; // bit will be inverted when stored + //} + + if (null == height) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); + } + + if (null == width) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); + } + + if (null == x) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); + } + + if (null == y) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); + } + + if (null == controlId) + { + controlId = this.Core.CreateIdentifier("ctl", dialog, x, y, height, width); + } + + if (isCancel) + { + cancelControl = controlId.Id; + } + + if (isDefault) + { + defaultControl = controlId.Id; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Binary": + this.ParseBinaryElement(child); + break; + case "ComboBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); + break; + case "ListBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); + break; + case "ListView": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "Publish": + this.ParsePublishElement(child, dialog ?? String.Empty, controlId.Id, ref publishOrder); + break; + case "RadioButtonGroup": + radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType); + break; + case "Subscribe": + this.ParseSubscribeElement(child, dialog, controlId.Id); + break; + case "Text": + foreach (var attrib in child.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + text = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(child, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(child, attrib); + } + } + + this.Core.InnerTextDisallowed(child); + + if (!String.IsNullOrEmpty(text) && null != sourceFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value")); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + this.Core.InnerTextDisallowed(node); + + // If the radio buttons have icons, then we need to add the icon attribute. + switch (radioButtonsType) + { + case RadioButtonType.Bitmap: + attributes |= WindowsInstallerConstants.MsidbControlAttributesBitmap; + break; + case RadioButtonType.Icon: + attributes |= WindowsInstallerConstants.MsidbControlAttributesIcon; + break; + case RadioButtonType.Text: + // Text is the default so nothing needs to be added bits + break; + } + + // the logic for creating control rows is a little tricky because of the way tabable controls are set + IntermediateSymbol symbol = null; + if (!this.Core.EncounteredError) + { + if ("CheckBox" == controlType) + { + if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true)); + } + else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef")); + } + else if (!String.IsNullOrEmpty(property)) + { + this.Core.AddSymbol(new CheckBoxSymbol(sourceLineNumbers) + { + Property = property, + Value = checkboxValue, + }); + } + else + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CheckBox, checkBoxPropertyRef); + } + } + + var id = new Identifier(controlId.Access, dialog, controlId.Id); + + if (SymbolDefinitionType.BBControl == symbolType) + { + var bbSymbol = this.Core.AddSymbol(new BBControlSymbol(sourceLineNumbers, id) + { + BillboardRef = dialog, + BBControl = controlId.Id, + Type = controlType, + Attributes = attributes, + Enabled = !disabled, + Indirect = indirect, + Integer = integer, + LeftScroll = leftScroll, + RightAligned = rightAligned, + RightToLeft = rightToLeft, + Sunken = sunken, + Visible = !hidden, + Text = text, + SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } + }); + + bbSymbol.Set((int)BBControlSymbolFields.X, x); + bbSymbol.Set((int)BBControlSymbolFields.Y, y); + bbSymbol.Set((int)BBControlSymbolFields.Width, width); + bbSymbol.Set((int)BBControlSymbolFields.Height, height); + + symbol = bbSymbol; + } + else + { + var controlSymbol = this.Core.AddSymbol(new ControlSymbol(sourceLineNumbers, id) + { + DialogRef = dialog, + Control = controlId.Id, + Type = controlType, + Attributes = attributes, + Enabled = !disabled, + Indirect = indirect, + Integer = integer, + LeftScroll = leftScroll, + RightAligned = rightAligned, + RightToLeft = rightToLeft, + Sunken = sunken, + Visible = !hidden, + Property = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef, + Text = text, + Help = (null == tooltip && null == help) ? null : String.Concat(tooltip, "|", help), // Separator is required, even if only one is non-null.}; + SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } + }); + + controlSymbol.Set((int)BBControlSymbolFields.X, x); + controlSymbol.Set((int)BBControlSymbolFields.Y, y); + controlSymbol.Set((int)BBControlSymbolFields.Width, width); + controlSymbol.Set((int)BBControlSymbolFields.Height, height); + + symbol = controlSymbol; + } + + if (!String.IsNullOrEmpty(defaultCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Default", + Condition = defaultCondition, + }); + } + + if (!String.IsNullOrEmpty(enableCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Enable", + Condition = enableCondition, + }); + } + + if (!String.IsNullOrEmpty(disableCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Disable", + Condition = disableCondition, + }); + } + + if (!String.IsNullOrEmpty(hideCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Hide", + Condition = hideCondition, + }); + } + + if (!String.IsNullOrEmpty(showCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Show", + Condition = showCondition, + }); + } + } + + if (!notTabbable) + { + if (symbol is ControlSymbol controlSymbol) + { + if (null != lastTabSymbol) + { + lastTabSymbol.NextControlRef = controlSymbol.Control; + } + lastTabSymbol = controlSymbol; + } + else if (symbol != null) + { + this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); + } + + if (null == firstControl) + { + firstControl = controlId.Id; + } + } + + // bitmap and icon controls contain a foreign key into the binary table in the text column; + // add a reference if the identifier of the binary entry is known during compilation + if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); + } + } + + /// + /// Parses a publish control event element. + /// + /// Element to parse. + /// Identifier of parent dialog. + /// Identifier of parent control. + /// Relative order of controls. + private void ParsePublishElement(XElement node, string dialog, string control, ref int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string argument = null; + string condition = null; + string controlEvent = null; + string property = null; + + // give this control event a unique ordering + order++; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Control": + if (null != control) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); + } + control = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Dialog": + if (null != dialog) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); + } + dialog = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, dialog); + break; + case "Event": + controlEvent = Compiler.UppercaseFirstChar(this.Core.GetAttributeValue(sourceLineNumbers, attrib)); + break; + case "Order": + order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647); + break; + case "Property": + property = String.Concat("[", this.Core.GetAttributeValue(sourceLineNumbers, attrib), "]"); + break; + case "Value": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == control) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); + } + + if (null == dialog) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog")); + } + + if (null == controlEvent && null == property) // need to specify at least one + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); + } + else if (null != controlEvent && null != property) // cannot specify both + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); + } + + if (null == argument) + { + if (null != controlEvent) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event")); + } + else if (null != property) + { + // if this is setting a property to null, put a special value in the argument column + argument = "{}"; + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ControlEventSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = control, + Event = controlEvent ?? property, + Argument = argument, + Condition = condition, + Ordering = order + }); + } + + if ("DoAction" == controlEvent && null != argument) + { + // if we're not looking at a standard action or a formatted string then create a reference + // to the custom action. + if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); + } + } + + // if we're referring to a dialog but not through a property, add it to the references + if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, argument); + } + } + + /// + /// Parses a control subscription element. + /// + /// Element to parse. + /// Identifier of dialog. + /// Identifier of control. + private void ParseSubscribeElement(XElement node, string dialog, string control) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string controlAttribute = null; + string eventMapping = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Attribute": + controlAttribute = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); + break; + case "Event": + eventMapping = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new EventMappingSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = control, + Event = eventMapping, + Attribute = controlAttribute, + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/ComponentKeyPath.cs b/src/wix/WixToolset.Core/ComponentKeyPath.cs new file mode 100644 index 00000000..8e9c5776 --- /dev/null +++ b/src/wix/WixToolset.Core/ComponentKeyPath.cs @@ -0,0 +1,25 @@ +// 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.Data; + + internal class ComponentKeyPath : IComponentKeyPath + { + /// + /// Identifier of the resource to be a key path. + /// + public string Id { get; set; } + + /// + /// Indicates whether the key path was explicitly set for this resource. + /// + public bool Explicit { get; set; } + + /// + /// Type of resource to be the key path. + /// + public PossibleKeyPathType Type { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/DecompileContext.cs b/src/wix/WixToolset.Core/DecompileContext.cs new file mode 100644 index 00000000..a7ec03fd --- /dev/null +++ b/src/wix/WixToolset.Core/DecompileContext.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileContext : IDecompileContext + { + internal DecompileContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string DecompilePath { get; set; } + + public OutputType DecompileType { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public string ExtractFolder { get; set; } + + public string CabinetExtractFolder { get; set; } + + public string BaseSourcePath { get; set; } + + public string IntermediateFolder { get; set; } + + public bool IsAdminImage { get; set; } + + public string OutputPath { get; set; } + + public bool SuppressCustomTables { get; set; } + + public bool SuppressDroppingEmptyTables { get; set; } + + public bool SuppressExtractCabinets { get; set; } + + public bool SuppressUI { get; set; } + + public bool TreatProductAsModule { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/DecompileResult.cs b/src/wix/WixToolset.Core/DecompileResult.cs new file mode 100644 index 00000000..fc24cab7 --- /dev/null +++ b/src/wix/WixToolset.Core/DecompileResult.cs @@ -0,0 +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.Core +{ + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class DecompileResult : IDecompileResult + { + public XDocument Document { get; set; } + + public IReadOnlyCollection ExtractedFilePaths { get; set; } + + public Platform? Platform { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Decompiler.cs b/src/wix/WixToolset.Core/Decompiler.cs new file mode 100644 index 00000000..859f582b --- /dev/null +++ b/src/wix/WixToolset.Core/Decompiler.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 +{ + using System; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Decompiler of the WiX toolset. + /// + internal class Decompiler : IDecompiler + { + internal Decompiler(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IDecompileResult Decompile(IDecompileContext context) + { + // Pre-decompile. + // + foreach (var extension in context.Extensions) + { + extension.PreDecompile(context); + } + + // Decompile. + // + var result = this.BackendDecompile(context); + + if (result != null) + { + // Post-decompile. + // + foreach (var extension in context.Extensions) + { + extension.PostDecompile(result); + } + } + + return result; + } + + private IDecompileResult BackendDecompile(IDecompileContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendFactories = extensionManager.GetServices(); + + foreach (var factory in backendFactories) + { + if (factory.TryCreateBackend(context.DecompileType.ToString(), context.OutputPath, out var backend)) + { + var result = backend.Decompile(context); + return result; + } + } + + // TODO: messaging that a backend could not be found to decompile the decompile type? + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs new file mode 100644 index 00000000..cfa78623 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs @@ -0,0 +1,176 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BackendHelper : IBackendHelper + { + private 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" }; + + public BackendHelper(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) + { + return new FileFacade(file, assembly); + } + + public IFileFacade CreateFileFacade(FileRow fileRow) + { + return new FileFacade(fileRow); + } + + public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) + { + return new FileFacade(true, fileSymbol); + } + + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) + { + var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source); + + var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination); + + return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer + { + Source = sourceFullPath, + Destination = destinationFullPath, + Move = move, + SourceLineNumbers = sourceLineNumbers, + Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase) + }; + } + + public string CreateGuid() + { + return Common.GenerateGuid(); + } + + public string CreateGuid(Guid namespaceGuid, string value) + { + return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); + } + + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) + { + return new ResolvedDirectory + { + DirectoryParent = directoryParent, + Name = name + }; + } + + public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) + { + var command = new ExtractEmbeddedFilesCommand(this, embeddedFiles); + command.Execute(); + + return command.TrackedFiles; + } + + public string GenerateIdentifier(string prefix, params string[] args) + { + return Common.GenerateIdentifier(prefix, args); + } + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) + { + return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); + } + + public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) + { + return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); + } + + public string GetMsiFileName(string value, bool source, bool longName) + { + return Common.GetName(value, source, longName); + } + + public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) + { + var command = new ResolveDelayedFieldsCommand(this.Messaging, delayedFields, variableCache); + command.Execute(); + } + + public string[] SplitMsiFileName(string value) + { + return Common.GetNames(value); + } + + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) + { + return new TrackedFile(path, type, sourceLineNumbers); + } + + public bool IsValidBinderVariable(string variable) + { + return Common.IsValidBinderVariable(variable); + } + + public bool IsValidFourPartVersion(string version) + { + return Common.IsValidFourPartVersion(version); + } + + public bool IsValidIdentifier(string id) + { + return Common.IsIdentifier(id); + } + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) + { + return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); + } + + public bool IsValidShortFilename(string filename, bool allowWildcards) + { + return Common.IsValidShortFilename(filename, allowWildcards); + } + + private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) + { + try + { + var result = Path.GetFullPath(path); + + var filename = Path.GetFileName(result); + + foreach (var reservedName in ReservedFileNames) + { + if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); + return null; + } + } + + return result; + } + catch (ArgumentException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); + } + catch (PathTooLongException) + { + this.Messaging.Write(ErrorMessages.PathTooLong(sourceLineNumbers, path)); + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs new file mode 100644 index 00000000..2340ed9e --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs @@ -0,0 +1,233 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExtensionManager : IExtensionManager + { + private const string UserWixFolderName = ".wix4"; + private const string MachineWixFolderName = "WixToolset4"; + private const string ExtensionsFolderName = "extensions"; + + private readonly List extensionFactories = new List(); + private readonly Dictionary> loadedExtensionsByType = new Dictionary>(); + + public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IWixToolsetCoreServiceProvider ServiceProvider { get; } + + public void Add(Assembly extensionAssembly) + { + var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t)); + var factories = types.Select(this.CreateExtensionFactory).ToList(); + + if (!factories.Any()) + { + var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath); + throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory.")); + } + + this.extensionFactories.AddRange(factories); + } + + public void Load(string extensionPath) + { + var checkPath = extensionPath; + var checkedPaths = new List { checkPath }; + try + { + if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath)) + { + if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion)) + { + foreach (var cachePath in this.CacheLocations()) + { + var extensionFolder = Path.Combine(cachePath, extensionId); + + var versionFolder = extensionVersion; + if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder)) + { + checkedPaths.Add(extensionFolder); + continue; + } + + checkPath = Path.Combine(extensionFolder, versionFolder, "tools", extensionId + ".dll"); + checkedPaths.Add(checkPath); + + if (TryLoadFromPath(checkPath, out assembly)) + { + break; + } + } + } + } + + if (assembly == null) + { + throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths)); + } + + this.Add(assembly); + } + catch (ReflectionTypeLoadException rtle) + { + throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString())))); + } + catch (WixException) + { + throw; + } + catch (Exception e) + { + throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e); + } + } + + public IReadOnlyCollection GetServices() where T : class + { + if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions)) + { + extensions = new List(); + + foreach (var factory in this.extensionFactories) + { + if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension) + { + extensions.Add(extension); + } + } + + this.loadedExtensionsByType.Add(typeof(T), extensions); + } + + return extensions.Cast().ToList(); + } + + private IExtensionFactory CreateExtensionFactory(Type type) + { + var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) }); + if (constructor != null) + { + return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider }); + } + + return (IExtensionFactory)Activator.CreateInstance(type); + } + + private IEnumerable CacheLocations() + { + var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + + path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + + if (Environment.Is64BitOperatingSystem) + { + path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + } + + path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + + path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + } + + private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion) + { + extensionId = extensionReference ?? String.Empty; + extensionVersion = String.Empty; + + var index = extensionId.LastIndexOf('/'); + if (index > 0) + { + extensionVersion = extensionReference.Substring(index + 1); + extensionId = extensionReference.Substring(0, index); + + if (!NuGet.Versioning.NuGetVersion.TryParse(extensionVersion, out _)) + { + return false; + } + + if (String.IsNullOrEmpty(extensionId)) + { + return false; + } + } + + return true; + } + + private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder) + { + foundVersionFolder = null; + + try + { + NuGet.Versioning.NuGetVersion version = null; + foreach (var versionPath in Directory.GetDirectories(basePath)) + { + var versionFolder = Path.GetFileName(versionPath); + if (NuGet.Versioning.NuGetVersion.TryParse(versionFolder, out var checkVersion) && + (version == null || version < checkVersion)) + { + foundVersionFolder = versionFolder; + version = checkVersion; + } + } + } + catch (IOException) + { + } + + return !String.IsNullOrEmpty(foundVersionFolder); + } + + private static bool TryLoadFromPath(string extensionPath, out Assembly assembly) + { + try + { + if (File.Exists(extensionPath)) + { + assembly = Assembly.LoadFrom(extensionPath); + return true; + } + } + catch (IOException e) when (e is FileLoadException || e is FileNotFoundException) + { + } + + assembly = null; + return false; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs new file mode 100644 index 00000000..f85d4842 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs @@ -0,0 +1,172 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + + internal class FileFacade : IFileFacade + { + public FileFacade(FileSymbol file, AssemblySymbol assembly) + { + this.FileSymbol = file; + this.AssemblySymbol = assembly; + + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; + } + + public FileFacade(bool fromModule, FileSymbol file) + { + this.FromModule = fromModule; + this.FileSymbol = file; + + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; + } + + public FileFacade(FileRow row) + { + this.FromTransform = true; + this.FileRow = row; + + this.Identifier = new Identifier(AccessModifier.Section, row.File); + this.ComponentRef = row.Component; + } + + public bool FromModule { get; } + + public bool FromTransform { get; } + + private FileRow FileRow { get; } + + private FileSymbol FileSymbol { get; } + + private AssemblySymbol AssemblySymbol { get; } + + public string Id => this.Identifier.Id; + + public Identifier Identifier { get; } + + public string ComponentRef { get; } + + public int DiskId + { + get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId; + set + { + if (this.FileRow == null) + { + this.FileSymbol.DiskId = value; + } + else + { + this.FileRow.DiskId = value; + } + } + } + + public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName; + + public int FileSize + { + get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize; + set + { + if (this.FileRow == null) + { + this.FileSymbol.FileSize = value; + } + else + { + this.FileRow.FileSize = value; + } + } + } + + public string Language + { + get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language; + set + { + if (this.FileRow == null) + { + this.FileSymbol.Language = value; + } + else + { + this.FileRow.Language = value; + } + } + } + + public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null; + + public int Sequence + { + get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence; + set + { + if (this.FileRow == null) + { + this.FileSymbol.Sequence = value; + } + else + { + this.FileRow.Sequence = value; + } + } + } + + public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; + + public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; + + public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; + + public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + + public string Version + { + get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version; + set + { + if (this.FileRow == null) + { + this.FileSymbol.Version = value; + } + else + { + this.FileRow.Version = value; + } + } + } + + public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null; + + public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException(); + + public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException(); + + /// + /// Gets the set of MsiAssemblyName rows created for this file. + /// + /// RowCollection of MsiAssemblyName table. + public List AssemblyNames { get; set; } + + /// + /// Gets or sets the MsiFileHash row for this file. + /// + public MsiFileHashSymbol Hash { get; set; } + + /// + /// Allows direct access to the underlying FileRow as requried for patching. + /// + public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs new file mode 100644 index 00000000..2cad7cce --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.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.ExtensibilityServices +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class FileTransfer : IFileTransfer + { + public string Source { get; set; } + + public string Destination { get; set; } + + public bool Move { get; set; } + + public SourceLineNumber SourceLineNumbers { get; set; } + + public bool Redundant { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs b/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs new file mode 100644 index 00000000..afcd9244 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs @@ -0,0 +1,99 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class Messaging : IMessaging + { + private IMessageListener listener; + private readonly HashSet suppressedWarnings = new HashSet(); + private readonly HashSet warningsAsErrors = new HashSet(); + + public bool EncounteredError { get; private set; } + + public int LastErrorNumber { get; private set; } + + public bool ShowVerboseMessages { get; set; } + + public bool SuppressAllWarnings { get; set; } + + public bool WarningsAsError { get; set; } + + public void ElevateWarningMessage(int warningNumber) => this.warningsAsErrors.Add(warningNumber); + + public void SetListener(IMessageListener listener) => this.listener = listener; + + public void SuppressWarningMessage(int warningNumber) => this.suppressedWarnings.Add(warningNumber); + + public void Write(Message message) + { + var level = this.CalculateMessageLevel(message); + + if (level == MessageLevel.Nothing) + { + return; + } + + if (level == MessageLevel.Error) + { + this.EncounteredError = true; + this.LastErrorNumber = message.Id; + } + + if (this.listener != null) + { + this.listener.Write(message); + } + else if (level == MessageLevel.Error) + { + throw new WixException(message); + } + } + + public void Write(string message, bool verbose = false) + { + if (!verbose || this.ShowVerboseMessages) + { + this.listener?.Write(message); + } + } + + /// + /// Determines the level of this message, when taking into account warning-as-error, + /// warning level, verbosity level and message suppressed by the caller. + /// + /// Event arguments for the message. + /// MessageLevel representing the level of this message. + private MessageLevel CalculateMessageLevel(Message message) + { + var level = message.Level; + + if (level == MessageLevel.Verbose) + { + if (!this.ShowVerboseMessages) + { + level = MessageLevel.Nothing; + } + } + else if (level == MessageLevel.Warning) + { + if (this.SuppressAllWarnings || this.suppressedWarnings.Contains(message.Id)) + { + level = MessageLevel.Nothing; + } + else if (this.WarningsAsError || this.warningsAsErrors.Contains(message.Id)) + { + level = MessageLevel.Error; + } + } + + level = this.listener?.CalculateMessageLevel(this, message, level) ?? level; + + return level; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs new file mode 100644 index 00000000..c1368190 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -0,0 +1,863 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ParseHelper : IParseHelper + { + public ParseHelper(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = serviceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private ISymbolDefinitionCreator Creator { get; set; } + + public bool ContainsProperty(string possibleProperty) + { + return Common.ContainsProperty(possibleProperty); + } + + public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) + { + + section.AddSymbol(new WixComplexReferenceSymbol(sourceLineNumbers) + { + Parent = parentId, + ParentType = parentType, + ParentLanguage = parentLanguage, + Child = childId, + ChildType = childType, + IsPrimary = isPrimary + }); + + this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); + } + + public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) + { + if (null == id) + { + id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); + } + + var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + ParentDirectoryRef = parentId, + Name = name, + ShortName = shortName, + SourceName = sourceName, + SourceShortName = shortSourceName + }); + + return symbol.Id; + } + + public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) + { + if (String.IsNullOrEmpty(parentId)) + { + throw new ArgumentNullException(nameof(parentId)); + } + + if (String.IsNullOrEmpty(inlineSyntax)) + { + inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true); + } + + if (String.IsNullOrEmpty(inlineSyntax)) + { + return parentId; + } + + inlineSyntax = inlineSyntax.Trim('\\', '/'); + + var cacheKey = String.Concat(parentId, ":", inlineSyntax); + + if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id)) + { + var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax); + + id = identifier.Id; + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); + } + + return id; + } + + public string CreateGuid(Guid namespaceGuid, string value) + { + return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); + } + + public Identifier CreateIdentifier(string prefix, params string[] args) + { + var id = Common.GenerateIdentifier(prefix, args); + return new Identifier(AccessModifier.Section, id); + } + + public Identifier CreateIdentifierFromFilename(string filename) + { + var id = Common.GetIdentifierFromName(filename); + return new Identifier(AccessModifier.Section, id); + } + + public string CreateIdentifierValueFromPlatform(string name, Platform currentPlatform, BurnPlatforms supportedPlatforms) + { + string suffix = null; + + switch (currentPlatform) + { + case Platform.X86: + if ((supportedPlatforms & BurnPlatforms.X86) == BurnPlatforms.X86) + { + suffix = "_X86"; + } + break; + case Platform.X64: + if ((supportedPlatforms & BurnPlatforms.X64) == BurnPlatforms.X64) + { + suffix = "_X64"; + } + break; + case Platform.ARM64: + if ((supportedPlatforms & BurnPlatforms.ARM64) == BurnPlatforms.ARM64) + { + suffix = "_A64"; + } + break; + } + + return suffix == null ? null : name + suffix; + } + + public Identifier CreateRegistrySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId, bool escapeLeadingHash) + { + if (RegistryRootType.Unknown == root) + { + throw new ArgumentOutOfRangeException(nameof(root)); + } + + if (null == key) + { + throw new ArgumentNullException(nameof(key)); + } + + if (null == componentId) + { + throw new ArgumentNullException(nameof(componentId)); + } + + // Escape the leading '#' character for string registry values. + if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal)) + { + value = String.Concat("#", value); + } + + var id = this.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name)); + + var symbol = section.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root, + Key = key, + Name = name, + Value = value, + ComponentRef = componentId, + }); + + return symbol.Id; + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) + { + section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) + { + Table = symbolName, + PrimaryKeys = primaryKey + }); + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) + { + section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) + { + Table = symbolName, + PrimaryKeys = String.Join("/", primaryKeys) + }); + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) + { + this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKey); + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) + { + this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKeys); + } + + public void CreateWixGroupSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) + { + if (null == parentId || ComplexReferenceParentType.Unknown == parentType) + { + return; + } + + if (null == childId) + { + throw new ArgumentNullException(nameof(childId)); + } + + section.AddSymbol(new WixGroupSymbol(sourceLineNumbers) + { + ParentId = parentId, + ParentType = parentType, + ChildId = childId, + ChildType = childType, + }); + } + + public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) + { + // TODO: verify variable is not a standard bundle variable + if (variable == null) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); + } + + section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id) + { + Variable = variable, + Condition = condition, + BundleExtensionRef = bundleExtensionId, + }); + + if (after != null) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixSearch, after); + // TODO: We're currently defaulting to "always run after", which we will need to change... + this.CreateWixSearchRelationSymbol(section, sourceLineNumbers, id, after, 2); + } + + if (!String.IsNullOrEmpty(bundleExtensionId)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixBundleExtension, bundleExtensionId); + } + } + + public void CreateWixSearchRelationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) + { + section.AddSymbol(new WixSearchRelationSymbol(sourceLineNumbers, id) + { + ParentSearchRef = parentId, + Attributes = attributes, + }); + } + + public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, Identifier identifier = null) + { + if (this.Creator == null) + { + this.CreateSymbolDefinitionCreator(); + } + + if (!this.Creator.TryGetSymbolDefinitionByName(symbolName, out var symbolDefinition)) + { + throw new ArgumentException(nameof(symbolName)); + } + + return this.CreateSymbol(section, sourceLineNumbers, symbolDefinition, identifier); + } + + public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, Identifier identifier = null) + { + return section.AddSymbol(symbolDefinition.CreateSymbol(sourceLineNumbers, identifier)); + } + + public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + { + section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) + { + Table = tableDefinition.Name, + }); + + // TODO: Check if the given table definition is a custom table. For now we have to assume that it isn't. + //this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableDefinition.Name); + } + + public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName) + { + section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) + { + Table = tableName, + }); + + if (this.Creator == null) + { + this.CreateSymbolDefinitionCreator(); + } + + // TODO: The tableName may not be the same as the symbolName. For now, we have to assume that it is. + // We don't add custom table definitions to the tableDefinitions collection, + // so if it's not in there, it better be a custom table. If the Id is just wrong, + // instead of a custom table, we get an unresolved reference at link time. + if (!this.Creator.TryGetSymbolDefinitionByName(tableName, out var _)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableName); + } + } + + public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) + { + if (null == attribute) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; + var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); + + if (String.IsNullOrEmpty(value)) + { + if (canBeEmpty) + { + return String.Empty; + } + } + else + { + if (generatable && value == "*") + { + return value; + } + + if (Guid.TryParse(value, out var guid)) + { + return guid.ToString("B").ToUpperInvariant(); + } + + if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) + { + return value; + } + + if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) || + value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return CompilerConstants.IllegalGuid; + } + + public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var access = AccessModifier.Global; + var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); + + var separator = value.IndexOf(' '); + if (separator > 0) + { + var prefix = value.Substring(0, separator); + switch (prefix) + { + case "global": + case "public": + case "package": + access = AccessModifier.Global; + break; + + case "internal": + case "library": + access = AccessModifier.Library; + break; + + case "file": + case "protected": + access = AccessModifier.File; + break; + + case "private": + case "fragment": + case "section": + access = AccessModifier.Section; + break; + + default: + return null; + } + + value = value.Substring(separator + 1).Trim(); + } + + if (!Common.IsIdentifier(value)) + { + this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + return null; + } + else if (72 < value.Length) + { + this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return new Identifier(access, value); + } + + public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); + } + + public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); + } + + public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (!String.IsNullOrEmpty(value)) + { + if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) + { + if (allowRelative) + { + this.Messaging.Write(ErrorMessages.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + else if (allowRelative) + { + value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value); + } + else if (CompilerCore.IsAmbiguousFilename(value)) + { + this.Messaging.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return value; + } + + public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) + { + Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing."); + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + try + { + var longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat); + + if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue) + { + this.Messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, longValue)); + } + else if (minimum > longValue || maximum < longValue) + { + this.Messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum)); + longValue = CompilerConstants.IllegalLong; + } + + return longValue; + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (OverflowException) + { + this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return CompilerConstants.IllegalLong; + } + + public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) + { + return Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, emptyRule); + } + + public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + if (String.IsNullOrEmpty(value)) + { + return null; + } + + switch (value) + { + case "HKCR": + return RegistryRootType.ClassesRoot; + + case "HKCU": + return RegistryRootType.CurrentUser; + + case "HKLM": + return RegistryRootType.LocalMachine; + + case "HKU": + return RegistryRootType.Users; + + case "HKMU": + if (allowHkmu) + { + return RegistryRootType.MachineUser; + } + break; + } + + if (allowHkmu) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKMU", "HKCR", "HKCU", "HKLM", "HKU")); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKCR", "HKCU", "HKLM", "HKU")); + } + + return RegistryRootType.Unknown; + } + + public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (!String.IsNullOrEmpty(value)) + { + if (Version.TryParse(value, out var version)) + { + return version.ToString(); + } + + // Allow versions to contain binder variables. + if (Common.ContainsValidBinderVariable(value)) + { + return value; + } + + this.Messaging.Write(ErrorMessages.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return null; + } + + public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + switch (value) + { + case "yes": + case "true": + return YesNoDefaultType.Yes; + + case "no": + case "false": + return YesNoDefaultType.No; + + case "default": + return YesNoDefaultType.Default; + + default: + this.Messaging.Write(ErrorMessages.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + return YesNoDefaultType.IllegalValue; + } + } + + public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + switch (value) + { + case "yes": + case "true": + return YesNoType.Yes; + + case "no": + case "false": + return YesNoType.No; + + default: + this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + return YesNoType.IllegalValue; + } + } + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) + { + return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); + } + + public SourceLineNumber GetSourceLineNumbers(XElement element) + { + return Preprocessor.GetSourceLineNumbers(element); + } + + public string GetConditionInnerText(XElement element) + { + var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); + + // Return null for a non-existant condition. + return String.IsNullOrEmpty(value) ? null : value; + } + + public string GetTrimmedInnerText(XElement element) + { + var value = Common.GetInnerText(element); + return value?.Trim(); + } + + public void InnerTextDisallowed(XElement element) + { + if (element.Nodes().Any(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType)) + { + var innerText = Common.GetInnerText(element); + if (!String.IsNullOrWhiteSpace(innerText)) + { + var sourceLineNumbers = this.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText)); + } + } + } + + public bool IsValidIdentifier(string value) + { + return Common.IsIdentifier(value); + } + + public bool IsValidLocIdentifier(string identifier) + { + return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc"; + } + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) + { + return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); + } + + public bool IsValidShortFilename(string filename, bool allowWildcards) + { + return Common.IsValidShortFilename(filename, allowWildcards); + } + + public void ParseExtensionAttribute(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary context = null) + { + // Ignore attributes defined by the W3C because we'll assume they are always right. + if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || + attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)) + { + return; + } + + if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension)) + { + extension.ParseAttribute(intermediate, section, element, attribute, context); + } + else + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName)); + } + } + + public void ParseExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context = null) + { + if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) + { + extension.ParseElement(intermediate, section, parentElement, element, context); + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); + } + } + + public IComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) + { + IComponentKeyPath keyPath = null; + + if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) + { + keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context); + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); + } + + return keyPath; + } + + public void ParseForExtensionElements(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element) + { + var checkInnerText = false; + + foreach (var child in element.Nodes()) + { + if (child is XElement childElement) + { + if (element.Name.Namespace == childElement.Name.Namespace) + { + this.UnexpectedElement(element, childElement); + } + else + { + this.ParseExtensionElement(extensions, intermediate, section, element, childElement); + } + } + else + { + checkInnerText = true; + } + } + + if (checkInnerText) + { + this.InnerTextDisallowed(element); + } + } + + public WixActionSymbol ScheduleActionSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition, string beforeAction, string afterAction, bool overridable = false) + { + var actionId = new Identifier(access, sequence, actionName); + + var actionSymbol = section.AddSymbol(new WixActionSymbol(sourceLineNumbers, actionId) + { + SequenceTable = sequence, + Action = actionName, + Condition = condition, + Before = beforeAction, + After = afterAction, + Overridable = overridable, + }); + + if (null != beforeAction) + { + if (WindowsInstallerStandard.IsStandardAction(beforeAction)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction); + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction); + } + } + + if (null != afterAction) + { + if (WindowsInstallerStandard.IsStandardAction(afterAction)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction); + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction); + } + } + + return actionSymbol; + } + + public void CreateCustomActionReference(SourceLineNumber sourceLineNumbers, IntermediateSection section, string customAction, Platform currentPlatform, CustomActionPlatforms supportedPlatforms) + { + if (!this.Messaging.EncounteredError) + { + var suffix = "_X86"; + + switch (currentPlatform) + { + case Platform.X64: + if ((supportedPlatforms & CustomActionPlatforms.X64) == CustomActionPlatforms.X64) + { + suffix = "_X64"; + } + break; + case Platform.ARM64: + if ((supportedPlatforms & CustomActionPlatforms.ARM64) == CustomActionPlatforms.ARM64) + { + suffix = "_A64"; + } + break; + } + + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, customAction + suffix); + } + } + + public void UnexpectedAttribute(XElement element, XAttribute attribute) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + Common.UnexpectedAttribute(this.Messaging, sourceLineNumbers, attribute); + } + + public void UnexpectedElement(XElement parentElement, XElement childElement) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement); + this.Messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName)); + } + + private void CreateSymbolDefinitionCreator() + { + this.Creator = this.ServiceProvider.GetService(); + } + + private static bool TryFindExtension(IEnumerable extensions, XNamespace ns, out ICompilerExtension extension) + { + extension = null; + + foreach (var ext in extensions) + { + if (ext.Namespace == ns) + { + extension = ext; + break; + } + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs new file mode 100644 index 00000000..72be2bcb --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class PathResolver : IPathResolver + { + public string GetCanonicalDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, Platform platform) + { + if (!directories.TryGetValue(directory, out var resolvedDirectory)) + { + throw new WixException(ErrorMessages.ExpectedDirectory(directory)); + } + + if (null == resolvedDirectory.Path) + { + if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) + { + resolvedDirectory.Path = componentIdGenSeeds[directory]; + } + else if (WindowsInstallerStandard.IsStandardDirectory(directory)) + { + resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform); + } + else + { + var name = resolvedDirectory.Name?.ToLowerInvariant(); + + if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) + { + resolvedDirectory.Path = name; + } + else + { + var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform); + + if (null != resolvedDirectory.Name) + { + resolvedDirectory.Path = Path.Combine(parentPath, name); + } + else + { + resolvedDirectory.Path = parentPath; + } + } + } + } + + return resolvedDirectory.Path; + } + + public string GetDirectoryPath(Dictionary directories, string directory) + { + if (!directories.TryGetValue(directory, out var resolvedDirectory)) + { + throw new WixException(ErrorMessages.ExpectedDirectory(directory)); + } + + if (null == resolvedDirectory.Path) + { + var name = resolvedDirectory.Name; + + if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) + { + resolvedDirectory.Path = name; + } + else + { + var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent); + + if (null != resolvedDirectory.Name) + { + resolvedDirectory.Path = Path.Combine(parentPath, name); + } + else + { + resolvedDirectory.Path = parentPath; + } + } + } + + return resolvedDirectory.Path; + } + + public string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) + { + var fileSourcePath = Common.GetName(fileName, true, useLongName); + + if (compressed) + { + // Use just the file name of the file since all uncompressed files must appear + // in the root of the image in a compressed package. + } + else + { + // Get the relative path of where we want the file to be layed out as specified + // in the Directory table. + var directoryPath = this.GetDirectoryPath(directories, directoryId); + fileSourcePath = Path.Combine(directoryPath, fileSourcePath); + } + + // Strip off "SourceDir" if it's still on there. + if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) + { + fileSourcePath = fileSourcePath.Substring(10); + } + + return fileSourcePath; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs new file mode 100644 index 00000000..b0c87bcf --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class PreprocessHelper : IPreprocessHelper + { + private static readonly char[] VariableSplitter = new char[] { '.' }; + private static readonly char[] ArgumentSplitter = new char[] { ',' }; + + public PreprocessHelper(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private Dictionary ExtensionsByPrefix { get; set; } + + public void AddVariable(IPreprocessContext context, string name, string value) + { + this.AddVariable(context, name, value, true); + } + + public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) + { + var currentValue = this.GetVariableValue(context, "var", name); + + if (null == currentValue) + { + context.Variables.Add(name, value); + } + else + { + if (showWarning && value != currentValue) + { + this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); + } + + context.Variables[name] = value; + } + } + + public string EvaluateFunction(IPreprocessContext context, string function) + { + var prefixParts = function.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) + { + throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var prefix = prefixParts[0]; + var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); + + // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. + if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) + { + throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var functionName = functionParts[0]; + + // Remove the trailing closing paren. + var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); + + // Parse the arguments and preprocess them. + var args = allArgs.Split(ArgumentSplitter); + for (var i = 0; i < args.Length; i++) + { + args[i] = this.PreprocessString(context, args[i].Trim()); + } + + var result = this.EvaluateFunction(context, prefix, functionName, args); + + // If the function didn't evaluate, try to evaluate the original value as a variable to support + // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. + if (result == null) + { + result = this.GetVariableValue(context, function, true); + } + + return result; + } + + public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(function)) + { + throw new ArgumentNullException("function"); + } + + switch (prefix) + { + case "fun": + switch (function) + { + case "AutoVersion": + // Make sure the base version is specified + if (args.Length == 0 || String.IsNullOrEmpty(args[0])) + { + throw new WixException(ErrorMessages.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); + } + + // Build = days since 1/1/2000; Revision = seconds since midnight / 2 + var now = DateTime.UtcNow; + var build = now - new DateTime(2000, 1, 1); + var revision = now - new DateTime(now.Year, now.Month, now.Day); + + return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); + + default: + return null; + } + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.EvaluateFunction(prefix, function, args); + } + catch (Exception e) + { + throw new WixException(ErrorMessages.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); + } + } + else + { + return null; + } + } + } + + public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) + { + // Strip the "$(" off the front and the ")" off the back. + if (variable.StartsWith("$(", StringComparison.Ordinal)) + { + variable = variable.Substring(2, variable.Length - 3); + } + + var parts = variable.Split(VariableSplitter, 2); + + if (1 == parts.Length) // missing prefix + { + if (allowMissingPrefix) + { + return this.GetVariableValue(context, "var", parts[0]); + } + else + { + throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + else + { + // check for empty variable name + if (0 < parts[1].Length) + { + string result = this.GetVariableValue(context, parts[0], parts[1]); + + // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix + if (null == result && allowMissingPrefix && variable.Contains(".")) + { + result = this.GetVariableValue(context, "var", variable); + } + + return result; + } + else + { + throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + } + + public string GetVariableValue(IPreprocessContext context, string prefix, string name) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + switch (prefix) + { + case "env": + return Environment.GetEnvironmentVariable(name); + + case "sys": + switch (name) + { + case "CURRENTDIR": + return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); + + case "SOURCEFILEDIR": + return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); + + case "SOURCEFILEPATH": + return context.CurrentSourceLineNumber.FileName; + + case "PLATFORM": + this.Messaging.Write(WarningMessages.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); + + goto case "BUILDARCH"; + + case "BUILDARCH": + switch (context.Platform) + { + case Platform.X86: + return "x86"; + + case Platform.X64: + return "x64"; + + case Platform.ARM64: + return "arm64"; + + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString()); + } + + case "WIXMAJORVERSION": + return ThisAssembly.AssemblyFileVersion.Split('.')[0]; + + case "WIXVERSION": + return ThisAssembly.AssemblyFileVersion; + + default: + return null; + } + + case "var": + return context.Variables.TryGetValue(name, out var result) ? result : null; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.GetVariableValue(prefix, name); + } + catch (Exception e) + { + throw new WixException(ErrorMessages.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); + } + } + else + { + return null; + } + } + } + + public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) + { + var prefixParts = pragmaName.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length) + { + throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + var prefix = prefixParts[0]; + var pragma = prefixParts[1]; + + if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) + { + throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + switch (prefix) + { + case "wix": + switch (pragma) + { + // Add any core defined pragmas here + default: + this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + break; + } + break; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + if (!extension.ProcessPragma(prefix, pragma, args, parent)) + { + this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + } + } + break; + } + } + + public string PreprocessString(IPreprocessContext context, string value) + { + var sb = new StringBuilder(); + var currentPosition = 0; + var end = 0; + + while (-1 != (currentPosition = value.IndexOf('$', end))) + { + if (end < currentPosition) + { + sb.Append(value, end, currentPosition - end); + } + + end = currentPosition + 1; + + var remainder = value.Substring(end); + if (remainder.StartsWith("$", StringComparison.Ordinal)) + { + sb.Append("$"); + end++; + } + else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) + { + currentPosition = remainder.IndexOf(')'); + if (-1 == currentPosition) + { + this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + + sb.Append("$"); // just put the resource reference back as was + sb.Append(remainder, 0, currentPosition + 1); + + end += currentPosition + 1; + } + else if (remainder.StartsWith("(", StringComparison.Ordinal)) + { + var openParenCount = 1; + var closingParenCount = 0; + var isFunction = false; + var foundClosingParen = false; + + // find the closing paren + int closingParenPosition; + for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) + { + switch (remainder[closingParenPosition]) + { + case '(': + openParenCount++; + isFunction = true; + break; + + case ')': + closingParenCount++; + break; + } + + if (openParenCount == closingParenCount) + { + foundClosingParen = true; + break; + } + } + + // Environment variables may contain parens so if it looks + // like a function, check to see if the environment variable + // prefix was explicitly provided. + if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal)) + { + isFunction = false; + } + + // move the currentPosition to the closing paren + currentPosition += closingParenPosition; + + if (!foundClosingParen) + { + if (isFunction) + { + this.Messaging.Write(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); + break; + } + else + { + this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + } + + var subString = remainder.Substring(1, closingParenPosition - 1); + string result = null; + if (isFunction) + { + result = this.EvaluateFunction(context, subString); + } + else + { + result = this.GetVariableValue(context, subString, true); + } + + if (null == result) + { + if (isFunction) + { + this.Messaging.Write(ErrorMessages.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); + break; + } + else + { + this.Messaging.Write(ErrorMessages.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); + break; + } + } + else + { + if (!isFunction) + { + //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); + } + } + + sb.Append(result); + end += closingParenPosition + 1; + } + else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing + { + sb.Append('$'); + } + } + + if (end < value.Length) + { + sb.Append(value.Substring(end)); + } + + return sb.ToString(); + } + + public void RemoveVariable(IPreprocessContext context, string name) + { + if (!context.Variables.Remove(name)) + { + this.Messaging.Write(ErrorMessages.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); + } + } + + private Dictionary GetExtensionsByPrefix() + { + if (this.ExtensionsByPrefix == null) + { + this.ExtensionsByPrefix = new Dictionary(); + + var extensionManager = this.ServiceProvider.GetService(); + + var extensions = extensionManager.GetServices(); + + foreach (var extension in extensions) + { + if (null != extension.Prefixes) + { + foreach (string prefix in extension.Prefixes) + { + if (!this.ExtensionsByPrefix.ContainsKey(prefix)) + { + this.ExtensionsByPrefix.Add(prefix, extension); + } + } + } + } + } + + return this.ExtensionsByPrefix; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs new file mode 100644 index 00000000..cc8acfdd --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs @@ -0,0 +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.Core.ExtensibilityServices +{ + using WixToolset.Extensibility.Data; + + internal class ResolvedDirectory : IResolvedDirectory + { + public string DirectoryParent { get; set; } + + public string Name { get; set; } + + public string Path { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs b/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs new file mode 100644 index 00000000..a2486130 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class SymbolDefinitionCreator : ISymbolDefinitionCreator + { + public SymbolDefinitionCreator(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + private IEnumerable ExtensionData { get; set; } + + private Dictionary CustomDefinitionByName { get; } = new Dictionary(); + + public void AddCustomSymbolDefinition(IntermediateSymbolDefinition definition) + { + if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision) + { + this.CustomDefinitionByName[definition.Name] = definition; + } + } + + public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) + { + // First, look in the built-ins. + symbolDefinition = SymbolDefinitions.ByName(name); + + if (symbolDefinition == null) + { + if (this.ExtensionData == null) + { + this.LoadExtensionData(); + } + + // Second, look in the extensions. + foreach (var data in this.ExtensionData) + { + if (data.TryGetSymbolDefinitionByName(name, out symbolDefinition)) + { + break; + } + } + + // Finally, look in the custom symbol definitions provided during an intermediate load. + if (symbolDefinition == null) + { + this.CustomDefinitionByName.TryGetValue(name, out symbolDefinition); + } + } + + return symbolDefinition != null; + } + + private void LoadExtensionData() + { + var extensionManager = this.ServiceProvider.GetService(); + + this.ExtensionData = extensionManager.GetServices(); + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs b/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs new file mode 100644 index 00000000..028cddbf --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs @@ -0,0 +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.Core.ExtensibilityServices +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class TrackedFile : ITrackedFile + { + public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers) + { + this.Path = path; + this.Type = type; + this.SourceLineNumbers = sourceLineNumbers; + this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final); + } + + public bool Clean { get; set; } + + public string Path { get; set; } + + public SourceLineNumber SourceLineNumbers { get; set; } + + public TrackedFileType Type { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs b/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs new file mode 100644 index 00000000..ad9eea26 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.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.ExtensibilityServices +{ + using System; + using System.Net; + using System.Security.Cryptography; + using System.Text; + + /// + /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. + /// + internal static class Uuid + { + /// + /// Creates a version 3 name-based UUID. + /// + /// The namespace UUID. + /// The value. + /// The UUID for the given namespace and value. + public static Guid NewUuid(Guid namespaceGuid, string value) + { + byte[] namespaceBytes = namespaceGuid.ToByteArray(); + short uuidVersion = (short)0x5000; + + // get the fields of the guid which are in host byte ordering + int timeLow = BitConverter.ToInt32(namespaceBytes, 0); + short timeMid = BitConverter.ToInt16(namespaceBytes, 4); + short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6); + + // convert to network byte ordering + timeLow = IPAddress.HostToNetworkOrder(timeLow); + timeMid = IPAddress.HostToNetworkOrder(timeMid); + timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion); + + // get the bytes from the value + byte[] valueBytes = Encoding.Unicode.GetBytes(value); + + // fill-in the hash input buffer + byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length]; + Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4); + Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2); + Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2); + Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8); + Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length); + + // perform the appropriate hash of the namespace and value + byte[] hash; + using (SHA1 sha1 = SHA1.Create()) + { + hash = sha1.ComputeHash(buffer); + } + + // get the fields of the hash which are in network byte ordering + timeLow = BitConverter.ToInt32(hash, 0); + timeMid = BitConverter.ToInt16(hash, 4); + timeHiAndVersion = BitConverter.ToInt16(hash, 6); + + // convert to network byte ordering + timeLow = IPAddress.NetworkToHostOrder(timeLow); + timeMid = IPAddress.NetworkToHostOrder(timeMid); + timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion); + + // set the version and variant bits + timeHiAndVersion &= 0x0FFF; + timeHiAndVersion += uuidVersion; + hash[8] &= 0x3F; + hash[8] |= 0x80; + + // put back the converted values into a 128-bit value + byte[] guidBits = new byte[16]; + Buffer.BlockCopy(hash, 0, guidBits, 0, 16); + + Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4); + Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2); + Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2); + + return new Guid(guidBits); + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs b/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs new file mode 100644 index 00000000..56300400 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs @@ -0,0 +1,124 @@ +// 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. + +using System.Resources; + +[assembly: NeutralResourcesLanguage("en-US")] + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Reflection; + using WixToolset.Extensibility.Services; + + /// + /// Branding strings. + /// + internal class WixBranding : IWixBranding + { + /// + /// News URL for the distribution. + /// + public static string NewsUrl = "http://wixtoolset.org/news/"; + + /// + /// Short product name for the distribution. + /// + public static string ShortProduct = "WiX Toolset"; + + /// + /// Support URL for the distribution. + /// + public static string SupportUrl = "http://wixtoolset.org/"; + + /// + /// Telemetry URL format for the distribution. + /// + public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}"; + + /// + /// VS Extensions Landing page Url for the distribution. + /// + public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; + + public string GetCreatingApplication() + { + return this.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])"); + } + + public string ReplacePlaceholders(string original, Assembly assembly = null) + { + if (assembly == null) + { + assembly = typeof(WixBranding).Assembly; + } + + var commonVersionPath = Path.Combine(Path.GetDirectoryName(typeof(WixBranding).Assembly.Location), "wixver.dll"); + if (File.Exists(commonVersionPath)) + { + var commonFileVersion = FileVersionInfo.GetVersionInfo(commonVersionPath); + + original = original.Replace("[FileCopyright]", commonFileVersion.LegalCopyright); + original = original.Replace("[FileVersion]", commonFileVersion.FileVersion); + } + + var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); + + original = original.Replace("[FileComments]", fileVersion.Comments); + original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); + original = original.Replace("[FileProductName]", fileVersion.ProductName); + original = original.Replace("[FileVersion]", fileVersion.FileVersion); + + if (original.Contains("[FileVersionMajorMinor]")) + { + var version = new Version(fileVersion.FileVersion); + original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); + } + + if (TryGetAttribute(assembly, out AssemblyCompanyAttribute company)) + { + original = original.Replace("[AssemblyCompany]", company.Company); + } + + if (TryGetAttribute(assembly, out AssemblyCopyrightAttribute copyright)) + { + original = original.Replace("[AssemblyCopyright]", copyright.Copyright); + } + + if (TryGetAttribute(assembly, out AssemblyDescriptionAttribute description)) + { + original = original.Replace("[AssemblyDescription]", description.Description); + } + + if (TryGetAttribute(assembly, out AssemblyProductAttribute product)) + { + original = original.Replace("[AssemblyProduct]", product.Product); + } + + if (TryGetAttribute(assembly, out AssemblyTitleAttribute title)) + { + original = original.Replace("[AssemblyTitle]", title.Title); + } + + original = original.Replace("[NewsUrl]", NewsUrl); + original = original.Replace("[ShortProduct]", ShortProduct); + original = original.Replace("[SupportUrl]", SupportUrl); + + return original; + } + + private static bool TryGetAttribute(Assembly assembly, out T attribute) where T : Attribute + { + attribute = null; + + var customAttributes = assembly.GetCustomAttributes(typeof(T), false); + if (null != customAttributes && 0 < customAttributes.Length) + { + attribute = customAttributes[0] as T; + } + + return null != attribute; + } + } +} diff --git a/src/wix/WixToolset.Core/IBinder.cs b/src/wix/WixToolset.Core/IBinder.cs new file mode 100644 index 00000000..a1b66f42 --- /dev/null +++ b/src/wix/WixToolset.Core/IBinder.cs @@ -0,0 +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.Core +{ + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface IBinder + { + IBindResult Bind(IBindContext context); + } +} diff --git a/src/wix/WixToolset.Core/ICompiler.cs b/src/wix/WixToolset.Core/ICompiler.cs new file mode 100644 index 00000000..0aae579a --- /dev/null +++ b/src/wix/WixToolset.Core/ICompiler.cs @@ -0,0 +1,13 @@ +// 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.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ICompiler + { + Intermediate Compile(ICompileContext context); + } +} diff --git a/src/wix/WixToolset.Core/IDecompiler.cs b/src/wix/WixToolset.Core/IDecompiler.cs new file mode 100644 index 00000000..74ec26de --- /dev/null +++ b/src/wix/WixToolset.Core/IDecompiler.cs @@ -0,0 +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.Core +{ + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface IDecompiler + { + IDecompileResult Decompile(IDecompileContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILayoutCreator.cs b/src/wix/WixToolset.Core/ILayoutCreator.cs new file mode 100644 index 00000000..cdff2a78 --- /dev/null +++ b/src/wix/WixToolset.Core/ILayoutCreator.cs @@ -0,0 +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.Core +{ + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ILayoutCreator + { + void Layout(ILayoutContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILibrarian.cs b/src/wix/WixToolset.Core/ILibrarian.cs new file mode 100644 index 00000000..0fcedea5 --- /dev/null +++ b/src/wix/WixToolset.Core/ILibrarian.cs @@ -0,0 +1,13 @@ +// 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.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ILibrarian + { + Intermediate Combine(ILibraryContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILinker.cs b/src/wix/WixToolset.Core/ILinker.cs new file mode 100644 index 00000000..11cc2c87 --- /dev/null +++ b/src/wix/WixToolset.Core/ILinker.cs @@ -0,0 +1,13 @@ +// 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.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ILinker + { + Intermediate Link(ILinkContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILocalizationParser.cs b/src/wix/WixToolset.Core/ILocalizationParser.cs new file mode 100644 index 00000000..0e70aa0e --- /dev/null +++ b/src/wix/WixToolset.Core/ILocalizationParser.cs @@ -0,0 +1,27 @@ +// 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.Xml.Linq; + using WixToolset.Data; + + /// + /// Parses localization source files. + /// + public interface ILocalizationParser + { + /// + /// Loads a localization file from a path on disk. + /// + /// Path to localization file saved on disk. + /// Returns the loaded localization file. + Localization ParseLocalization(string path); + + /// + /// Loads a localization file from memory. + /// + /// Document to parse as localization file. + /// Returns the loaded localization file. + Localization ParseLocalization(XDocument document); + } +} diff --git a/src/wix/WixToolset.Core/IPreprocessor.cs b/src/wix/WixToolset.Core/IPreprocessor.cs new file mode 100644 index 00000000..f6ed5fed --- /dev/null +++ b/src/wix/WixToolset.Core/IPreprocessor.cs @@ -0,0 +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.Core +{ + using System.Xml; + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation, move into Extensibility + public interface IPreprocessor + { + IPreprocessResult Preprocess(IPreprocessContext context); + + IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader); + } +} diff --git a/src/wix/WixToolset.Core/IResolver.cs b/src/wix/WixToolset.Core/IResolver.cs new file mode 100644 index 00000000..db25edbe --- /dev/null +++ b/src/wix/WixToolset.Core/IResolver.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 WixToolset.Extensibility.Data; + + /// + /// Resolves localization and bind variables. + /// + public interface IResolver + { + /// + /// Resolve localization and bind variables. + /// + /// Resolve context. + /// Resolve result. + IResolveResult Resolve(IResolveContext context); + } +} diff --git a/src/wix/WixToolset.Core/IUnbinder.cs b/src/wix/WixToolset.Core/IUnbinder.cs new file mode 100644 index 00000000..2b4daaa5 --- /dev/null +++ b/src/wix/WixToolset.Core/IUnbinder.cs @@ -0,0 +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.Core +{ + using WixToolset.Data; + +#pragma warning disable 1591 // TODO: add documentation, move into Extensibility + public interface IUnbinder + { + Intermediate Unbind(string file, OutputType outputType, string exportBasePath); + } +} diff --git a/src/wix/WixToolset.Core/IncludedFile.cs b/src/wix/WixToolset.Core/IncludedFile.cs new file mode 100644 index 00000000..25d51191 --- /dev/null +++ b/src/wix/WixToolset.Core/IncludedFile.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 +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class IncludedFile : IIncludedFile + { + public string Path { get; set; } + + public SourceLineNumber SourceLineNumbers { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/IncribeContext.cs b/src/wix/WixToolset.Core/IncribeContext.cs new file mode 100644 index 00000000..9d7055ab --- /dev/null +++ b/src/wix/WixToolset.Core/IncribeContext.cs @@ -0,0 +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.Core +{ + using System; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class InscribeContext : IInscribeContext + { + public InscribeContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string IntermediateFolder { get; set; } + + public string InputFilePath { get; set; } + + public string SignedEngineFile { get; set; } + + public string OutputFile { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/LayoutContext.cs b/src/wix/WixToolset.Core/LayoutContext.cs new file mode 100644 index 00000000..4b8c7b99 --- /dev/null +++ b/src/wix/WixToolset.Core/LayoutContext.cs @@ -0,0 +1,40 @@ +// 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.Collections.Generic; + using System.Threading; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class LayoutContext : ILayoutContext + { + internal LayoutContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection FileSystemExtensions { get; set; } + + public IReadOnlyCollection FileTransfers { get; set; } + + public IReadOnlyCollection TrackedFiles { get; set; } + + public string IntermediateFolder { get; set; } + + public string ContentsFile { get; set; } + + public string OutputsFile { get; set; } + + public string BuiltOutputsFile { get; set; } + + public bool ResetAcls { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/LayoutCreator.cs b/src/wix/WixToolset.Core/LayoutCreator.cs new file mode 100644 index 00000000..0c5aaf63 --- /dev/null +++ b/src/wix/WixToolset.Core/LayoutCreator.cs @@ -0,0 +1,223 @@ +// 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.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Layout for the WiX toolset. + /// + internal class LayoutCreator : ILayoutCreator + { + internal LayoutCreator(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public void Layout(ILayoutContext context) + { + // Pre-layout. + // + foreach (var extension in context.Extensions) + { + extension.PreLayout(context); + } + + try + { + // Final step in binding that transfers (moves/copies) all files generated into the appropriate + // location in the source image. + if (context.FileTransfers?.Any() == true) + { + this.Messaging.Write(VerboseMessages.LayingOutMedia()); + + var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls); + command.Execute(); + } + + if (context.TrackedFiles != null) + { + this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles); + } + } + finally + { + if (context.TrackedFiles != null) + { + if (!String.IsNullOrEmpty(context.ContentsFile)) + { + this.CreateContentsFile(context.ContentsFile, context.TrackedFiles); + } + + if (!String.IsNullOrEmpty(context.OutputsFile)) + { + this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles); + } + + if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) + { + this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles); + } + } + } + + // Post-layout. + foreach (var extension in context.Extensions) + { + extension.PostLayout(); + } + } + + /// + /// Writes the paths to the content files to a text file. + /// + /// Path to write file. + /// Collection of paths to content files that will be written to file. + private void CreateContentsFile(string path, IEnumerable trackedFiles) + { + var uniqueInputFilePaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueInputFilePaths.Any()) + { + return; + } + + var directory = Path.GetDirectoryName(path); + Directory.CreateDirectory(directory); + + using (var contents = new StreamWriter(path, false)) + { + foreach (var inputPath in uniqueInputFilePaths) + { + contents.WriteLine(inputPath); + } + } + } + + /// + /// Writes the paths to the output files to a text file. + /// + /// Path to write file. + /// Collection of files that were transferred to the output directory. + private void CreateOutputsFile(string path, IEnumerable trackedFiles) + { + var uniqueOutputPaths = new SortedSet(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueOutputPaths.Any()) + { + return; + } + + var directory = Path.GetDirectoryName(path); + Directory.CreateDirectory(directory); + + using (var outputs = new StreamWriter(path, false)) + { + //// Don't list files where the source is the same as the destination since + //// that might be the only place the file exists. The outputs file is often + //// used to delete stuff and losing the original source would be bad. + //var uniqueOutputPaths = new SortedSet(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase); + + foreach (var outputPath in uniqueOutputPaths) + { + outputs.WriteLine(outputPath); + } + } + } + + /// + /// Writes the paths to the built output files to a text file. + /// + /// Path to write file. + /// Collection of files that were transferred to the output directory. + private void CreateBuiltOutputsFile(string path, IEnumerable trackedFiles) + { + var uniqueBuiltPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueBuiltPaths.Any()) + { + return; + } + + var directory = Path.GetDirectoryName(path); + Directory.CreateDirectory(directory); + + using (var outputs = new StreamWriter(path, false)) + { + foreach (var builtPath in uniqueBuiltPaths) + { + outputs.WriteLine(builtPath); + } + } + } + + private void CleanTempFiles(string intermediateFolder, IEnumerable trackedFiles) + { + var uniqueTempPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueTempPaths.Any()) + { + return; + } + + var uniqueFolders = new SortedSet(StringComparer.OrdinalIgnoreCase) + { + intermediateFolder + }; + + // Clean up temp files. + foreach (var tempPath in uniqueTempPaths) + { + try + { + this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders); + + File.Delete(tempPath); + } + catch // delete is best effort. + { + } + } + + // Clean up empty temp folders. + foreach (var folder in uniqueFolders.Reverse()) + { + try + { + Directory.Delete(folder); + } + catch // delete is best effort. + { + } + } + } + + private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet uniqueFolders) + { + if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase)) + { + var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length); + + var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); + + folder = intermediateFolder; + + foreach (var part in parts) + { + folder = Path.Combine(folder, part); + + uniqueFolders.Add(folder); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/Librarian.cs b/src/wix/WixToolset.Core/Librarian.cs new file mode 100644 index 00000000..1dd1b44d --- /dev/null +++ b/src/wix/WixToolset.Core/Librarian.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 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Core.Link; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Core librarian tool. + /// + internal class Librarian : ILibrarian + { + internal Librarian(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + /// + /// Create a library by combining several intermediates (objects). + /// + /// Returns the new library. + public Intermediate Combine(ILibraryContext context) + { + if (String.IsNullOrEmpty(context.LibraryId)) + { + context.LibraryId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_'); + } + + foreach (var extension in context.Extensions) + { + extension.PreCombine(context); + } + + Intermediate library = null; + try + { + var sections = context.Intermediates.SelectMany(i => i.Sections).ToList(); + + var collate = new CollateLocalizationsCommand(this.Messaging, context.Localizations); + var localizationsByCulture = collate.Execute(); + + if (this.Messaging.EncounteredError) + { + return null; + } + + this.ResolveFilePathsToEmbed(context, sections); + + foreach (var section in sections) + { + section.AssignToLibrary(context.LibraryId); + } + + library = new Intermediate(context.LibraryId, IntermediateLevels.Compiled, sections, localizationsByCulture); + + library.UpdateLevel(IntermediateLevels.Combined); + + this.Validate(library); + } + finally + { + foreach (var extension in context.Extensions) + { + extension.PostCombine(library); + } + } + + return this.Messaging.EncounteredError ? null : library; + } + + private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable sections) + { + // Resolve paths to files that are to be embedded in the library. + if (context.BindFiles) + { + var variableResolver = this.ServiceProvider.GetService(); + + var fileResolver = new FileResolver(context.BindPaths, context.Extensions); + + foreach (var symbol in sections.SelectMany(s => s.Symbols)) + { + foreach (var field in symbol.Fields.Where(f => f?.Type == IntermediateFieldType.Path)) + { + var pathField = field.AsPath(); + + if (pathField != null && !String.IsNullOrEmpty(pathField.Path)) + { + var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); + + var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); + + if (!String.IsNullOrEmpty(file)) + { + // File was successfully resolved so track the embedded index as the embedded file index. + field.Set(new IntermediateFieldPathValue { Embed = true, Path = file }); + } + else + { + this.Messaging.Write(ErrorMessages.FileNotFound(symbol.SourceLineNumbers, pathField.Path, symbol.Definition.Name)); + } + } + } + } + } + } + + private void Validate(Intermediate library) + { + var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, library.Sections, OutputType.Library); + find.Execute(); + + // TODO: Consider bringing this sort of verification back. + // foreach (Section section in library.Sections) + // { + // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols); + // resolve.Execute(); + // + // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections); + // reportDupes.Execute(); + // } + } + } +} diff --git a/src/wix/WixToolset.Core/LibraryContext.cs b/src/wix/WixToolset.Core/LibraryContext.cs new file mode 100644 index 00000000..e701cadf --- /dev/null +++ b/src/wix/WixToolset.Core/LibraryContext.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Core +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class LibraryContext : ILibraryContext + { + internal LibraryContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; set; } + + public bool BindFiles { get; set; } + + public IReadOnlyCollection BindPaths { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public string LibraryId { get; set; } + + public IReadOnlyCollection Localizations { get; set; } + + public IReadOnlyCollection Intermediates { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs b/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs new file mode 100644 index 00000000..d5c69838 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs @@ -0,0 +1,71 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class CollateLocalizationsCommand + { + public CollateLocalizationsCommand(IMessaging messaging, IEnumerable localizations) + { + this.Messaging = messaging; + this.Localizations = localizations; + } + + private IMessaging Messaging { get; } + + private IEnumerable Localizations { get; } + + public Dictionary Execute() + { + var localizationsByCulture = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var localization in this.Localizations) + { + if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture)) + { + var merged = this.Merge(existingCulture, localization); + localizationsByCulture[localization.Culture] = merged; + } + else + { + localizationsByCulture.Add(localization.Culture, localization); + } + } + + return localizationsByCulture; + } + + private Localization Merge(Localization existingLocalization, Localization localization) + { + var variables = existingLocalization.Variables.ToDictionary(v => v.Id); + var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + foreach (var newVariable in localization.Variables) + { + if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable)) + { + variables[newVariable.Id] = newVariable; + } + else if (!newVariable.Overridable) + { + this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id)); + } + } + + foreach (var localizedControl in localization.LocalizedControls) + { + if (!controls.ContainsKey(localizedControl.Key)) + { + controls.Add(localizedControl.Key, localizedControl.Value); + } + } + + return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToFeature.cs b/src/wix/WixToolset.Core/Link/ConnectToFeature.cs new file mode 100644 index 00000000..e9a739a1 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToFeature.cs @@ -0,0 +1,59 @@ +// 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.Link +{ + using System.Collections.Generic; + using WixToolset.Data; + + /// + /// Object that connects things (components/modules) to features. + /// + internal class ConnectToFeature + { + /// + /// Creates a new connect to feature. + /// + /// Section this connect belongs to. + /// Id of the child. + /// Sets the primary feature for the connection. + /// Sets if this is explicit primary. + public ConnectToFeature(IntermediateSection section, string childId, string primaryFeature, bool explicitPrimaryFeature) + { + this.Section = section; + this.ChildId = childId; + + this.PrimaryFeature = primaryFeature; + this.IsExplicitPrimaryFeature = explicitPrimaryFeature; + } + + /// + /// Gets the section. + /// + /// Section. + public IntermediateSection Section { get; } + + /// + /// Gets the child identifier. + /// + /// The child identifier. + public string ChildId { get; } + + /// + /// Gets or sets if the flag for if the primary feature was set explicitly. + /// + /// The flag for if the primary feature was set explicitly. + public bool IsExplicitPrimaryFeature { get; set; } + + /// + /// Gets or sets the primary feature. + /// + /// The primary feature. + public string PrimaryFeature { get; set; } + + /// + /// Gets the features connected to. + /// + /// Features connected to. + public List ConnectFeatures { get; } = new List(); + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs new file mode 100644 index 00000000..b7874527 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.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.Link +{ + using System; + using System.Collections; + + /// + /// Hash collection of connect to feature objects. + /// + internal class ConnectToFeatureCollection : ICollection + { + private Hashtable collection; + + /// + /// Instantiate a new ConnectToFeatureCollection class. + /// + public ConnectToFeatureCollection() + { + this.collection = new Hashtable(); + } + + /// + /// Gets the number of items in the collection. + /// + /// Number of items in collection. + public int Count + { + get { return this.collection.Count; } + } + + /// + /// Gets if the collection has been synchronized. + /// + /// True if the collection has been synchronized. + public bool IsSynchronized + { + get { return this.collection.IsSynchronized; } + } + + /// + /// Gets the object used to synchronize the collection. + /// + /// Oject used the synchronize the collection. + public object SyncRoot + { + get { return this.collection.SyncRoot; } + } + + /// + /// Gets a feature connection by child id. + /// + /// Identifier of child to locate. + public ConnectToFeature this[string childId] + { + get { return (ConnectToFeature)this.collection[childId]; } + } + + /// + /// Adds a feature connection to the collection. + /// + /// Feature connection to add. + public void Add(ConnectToFeature connection) + { + if (null == connection) + { + throw new ArgumentNullException("connection"); + } + + this.collection.Add(connection.ChildId, connection); + } + + /// + /// Copies the collection into an array. + /// + /// Array to copy the collection into. + /// Index to start copying from. + public void CopyTo(System.Array array, int index) + { + this.collection.CopyTo(array, index); + } + + /// + /// Gets enumerator for the collection. + /// + /// Enumerator for the collection. + public IEnumerator GetEnumerator() + { + return this.collection.Values.GetEnumerator(); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToModule.cs b/src/wix/WixToolset.Core/Link/ConnectToModule.cs new file mode 100644 index 00000000..4380e12c --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToModule.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.Link +{ + /// + /// Object that connects things to modules. + /// + internal class ConnectToModule + { + private string childId; + private string module; + private string moduleLanguage; + + /// + /// Creates a new connect to module. + /// + /// Id of the child. + /// Id of the module. + /// Language of the module. + public ConnectToModule(string childId, string module, string moduleLanguage) + { + this.childId = childId; + this.module = module; + this.moduleLanguage = moduleLanguage; + } + + /// + /// Gets the id of the child. + /// + /// Child identifier. + public string ChildId + { + get { return this.childId; } + } + + /// + /// Gets the id of the module. + /// + /// The id of the module. + public string Module + { + get { return this.module; } + } + + /// + /// Gets the language of the module. + /// + /// The language of the module. + public string ModuleLanguage + { + get { return this.moduleLanguage; } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs new file mode 100644 index 00000000..e0f96ffb --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.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.Link +{ + using System; + using System.Collections; + + /// + /// Hash collection of connect to module objects. + /// + internal class ConnectToModuleCollection : ICollection + { + private Hashtable collection; + + /// + /// Instantiate a new ConnectToModuleCollection class. + /// + public ConnectToModuleCollection() + { + this.collection = new Hashtable(); + } + + /// + /// Gets the number of elements actually contained in the ConnectToModuleCollection. + /// + /// The number of elements actually contained in the ConnectToModuleCollection. + public int Count + { + get { return this.collection.Count; } + } + + /// + /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe). + /// + /// true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false. + public bool IsSynchronized + { + get { return this.collection.IsSynchronized; } + } + + /// + /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection. + /// + /// An object that can be used to synchronize access to the ConnectToModuleCollection. + public object SyncRoot + { + get { return this.collection.SyncRoot; } + } + + /// + /// Gets a module connection by child id. + /// + /// Identifier of child to locate. + public ConnectToModule this[string childId] + { + get { return (ConnectToModule)this.collection[childId]; } + } + + /// + /// Adds a module connection to the collection. + /// + /// Module connection to add. + public void Add(ConnectToModule connection) + { + if (null == connection) + { + throw new ArgumentNullException("connection"); + } + + this.collection.Add(connection.ChildId, connection); + } + + /// + /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array. + /// + /// The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing. + /// The zero-based index in array at which copying begins. + public void CopyTo(System.Array array, int index) + { + this.collection.Keys.CopyTo(array, index); + } + + /// + /// Returns an enumerator for the entire ConnectToModuleCollection. + /// + /// An IEnumerator for the entire ConnectToModuleCollection. + public IEnumerator GetEnumerator() + { + return this.collection.Keys.GetEnumerator(); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs new file mode 100644 index 00000000..5d6cc831 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs @@ -0,0 +1,119 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class FindEntrySectionAndLoadSymbolsCommand + { + public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable sections, OutputType expectedOutpuType) + { + this.Messaging = messaging; + this.Sections = sections; + this.ExpectedOutputType = expectedOutpuType; + } + + private IMessaging Messaging { get; } + + private IEnumerable Sections { get; } + + private OutputType ExpectedOutputType { get; } + + /// + /// Gets the located entry section after the command is executed. + /// + public IntermediateSection EntrySection { get; private set; } + + /// + /// Gets the collection of loaded symbols. + /// + public IDictionary SymbolsByName { get; private set; } + + /// + /// Gets the collection of possibly conflicting symbols. + /// + public IEnumerable PossibleConflicts { get; private set; } + + /// + /// Gets the collection of redundant symbols that should not be included + /// in the final output. + /// + public ISet RedundantSymbols { get; private set; } + + public void Execute() + { + var symbolsByName = new Dictionary(); + var possibleConflicts = new HashSet(); + var redundantSymbols = new HashSet(); + + if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType)) + { + expectedEntrySectionType = SectionType.Unknown; + } + + foreach (var section in this.Sections) + { + // Try to find the one and only entry section. + if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type) + { + // TODO: remove this? + //if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType) + //{ + // string outputExtension = Output.GetExtension(this.ExpectedOutputType); + // this.Messaging.Write(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension)); + //} + + if (null == this.EntrySection) + { + this.EntrySection = section; + } + else + { + this.Messaging.Write(ErrorMessages.MultipleEntrySections(this.EntrySection.Symbols.FirstOrDefault()?.SourceLineNumbers, this.EntrySection.Id, section.Id)); + this.Messaging.Write(ErrorMessages.MultipleEntrySections2(section.Symbols.FirstOrDefault()?.SourceLineNumbers)); + } + } + + // Load all the symbols from the section's tables that create symbols. + foreach (var symbol in section.Symbols.Where(t => t.Id != null)) + { + var symbolWithSection = new SymbolWithSection(section, symbol); + + if (!symbolsByName.TryGetValue(symbolWithSection.Name, out var existingSymbol)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + else // uh-oh, duplicate symbols. + { + // If the duplicate symbols are both private directories, there is a chance that they + // point to identical symbols. Identical directory symbols are redundant and will not cause + // conflicts. + if (AccessModifier.Section == existingSymbol.Access && AccessModifier.Section == symbolWithSection.Access && + SymbolDefinitionType.Directory == existingSymbol.Symbol.Definition.Type && existingSymbol.Symbol.IsIdentical(symbolWithSection.Symbol)) + { + // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be + // referenced into the final output) it will not add duplicate primary keys during + // the .IDT importing. + existingSymbol.AddRedundant(symbolWithSection); + redundantSymbols.Add(symbolWithSection.Symbol); + } + else + { + symbolWithSection.AddPossibleConflict(existingSymbol); + existingSymbol.AddPossibleConflict(symbolWithSection); + possibleConflicts.Add(symbolWithSection); + } + } + } + } + + this.SymbolsByName = symbolsByName; + this.PossibleConflicts = possibleConflicts; + this.RedundantSymbols = redundantSymbols; + } + } +} diff --git a/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs new file mode 100644 index 00000000..16593c7d --- /dev/null +++ b/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs @@ -0,0 +1,194 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class FlattenAndProcessBundleTablesCommand + { + public FlattenAndProcessBundleTablesCommand(IntermediateSection entrySection, IMessaging messaging) + { + this.EntrySection = entrySection; + this.Messaging = messaging; + } + + private IntermediateSection EntrySection { get; } + + private IMessaging Messaging { get; } + + public void Execute() + { + this.FlattenBundleTables(); + + if (this.Messaging.EncounteredError) + { + return; + } + + this.ProcessBundleComplexReferences(); + } + + /// + /// Flattens the tables used in a Bundle. + /// + private void FlattenBundleTables() + { + // We need to flatten the nested PayloadGroups and PackageGroups under + // UX, Chain, and any Containers. When we're done, the WixGroups table + // will hold Payloads under UX, ChainPackages (references?) under Chain, + // and ContainerPackages/Payloads under any authored Containers. + var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging); + + // Create UX payloads and Package payloads and Container packages + groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, + new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); + groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); + groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); + groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); + + // Create Chain packages... + groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); + groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, false); + + groups.RemoveUsedGroupRows(); + } + + private void ProcessBundleComplexReferences() + { + var containersById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); + var groups = this.EntrySection.Symbols.OfType().ToList(); + var payloadsById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); + + var containerByPackage = new Dictionary(); + var referencedPackages = new HashSet(); + var payloadsInBA = new HashSet(); + var payloadsInPackageOrLayout = new HashSet(); + + foreach (var groupSymbol in groups) + { + switch (groupSymbol.ChildType) + { + case ComplexReferenceChildType.ContainerPackage: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.Container: + if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer)) + { + this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id)); + } + else + { + containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]); + } + break; + } + break; + case ComplexReferenceChildType.Package: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.PackageGroup: + if (groupSymbol.ParentId == BurnConstants.BundleChainPackageGroupId) + { + referencedPackages.Add(groupSymbol.ChildId); + } + break; + } + break; + case ComplexReferenceChildType.Payload: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.Container: + if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName) + { + payloadsInBA.Add(groupSymbol.ChildId); + } + break; + case ComplexReferenceParentType.Layout: + payloadsById[groupSymbol.ChildId].LayoutOnly = true; + payloadsInPackageOrLayout.Add(groupSymbol.ChildId); + break; + case ComplexReferenceParentType.Package: + payloadsInPackageOrLayout.Add(groupSymbol.ChildId); + break; + } + break; + } + } + + foreach (var package in this.EntrySection.Symbols.OfType()) + { + if (!referencedPackages.Contains(package.Id.Id)) + { + this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id)); + } + } + + foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType()) + { + if (!referencedPackages.Contains(rollbackBoundary.Id.Id)) + { + this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id)); + } + } + + foreach (var payload in payloadsById.Values) + { + var payloadId = payload.Id.Id; + if (payloadsInBA.Contains(payloadId)) + { + if (payloadsInPackageOrLayout.Contains(payloadId)) + { + this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId)); + } + } + else if (!payloadsInPackageOrLayout.Contains(payloadId)) + { + this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId)); + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Assign authored payloads to authored containers. + // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding. + foreach (var groupSymbol in groups) + { + if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container) + { + var payloadSymbol = payloadsById[groupSymbol.ChildId]; + var containerId = groupSymbol.ParentId; + + if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) + { + if (payloadSymbol.Compressed == false) + { + this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); + } + + payloadSymbol.Compressed = true; + payloadSymbol.ContainerRef = containerId; + } + else + { + this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef)); + } + + if (payloadSymbol.LayoutOnly) + { + this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); + } + } + } + + } + } +} diff --git a/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs b/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs new file mode 100644 index 00000000..cbf48abe --- /dev/null +++ b/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs @@ -0,0 +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.Core.Link +{ + using WixToolset.Data; + + internal static class IntermediateSymbolExtensions + { + public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second) + { + var identical = (first.Definition.Type == second.Definition.Type && + (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) && + first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length); + + for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) + { + var firstField = first[i]; + var secondField = second[i]; + + identical = (firstField.AsString() == secondField.AsString()); + } + + return identical; + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs new file mode 100644 index 00000000..ace2e19d --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.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.Link +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class ReportConflictingSymbolsCommand + { + public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable possibleConflicts, IEnumerable resolvedSections) + { + this.Messaging = messaging; + this.PossibleConflicts = possibleConflicts; + this.ResolvedSections = resolvedSections; + } + + private IMessaging Messaging { get; } + + private IEnumerable PossibleConflicts { get; } + + private IEnumerable ResolvedSections { get; } + + public void Execute() + { + // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow + // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should + // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting + // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate + // (aka: conflicting) symbol. + var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList(); + if (0 < illegalDuplicates.Count) + { + var referencedSections = new HashSet(this.ResolvedSections); + + foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section))) + { + var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList(); + + if (actuallyReferencedDuplicates.Any()) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, referencedDuplicate.Name)); + + foreach (var duplicate in actuallyReferencedDuplicates) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers)); + } + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs new file mode 100644 index 00000000..efb90bb8 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs @@ -0,0 +1,183 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + /// + /// Resolves all the simple references in a section. + /// + internal class ResolveReferencesCommand + { + private readonly IntermediateSection entrySection; + private readonly IDictionary symbolsWithSections; + private HashSet referencedSymbols; + private HashSet resolvedSections; + + public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary symbolsWithSections) + { + this.Messaging = messaging; + this.entrySection = entrySection; + this.symbolsWithSections = symbolsWithSections; + this.BuildingMergeModule = (SectionType.Module == entrySection.Type); + } + + public IEnumerable ReferencedSymbolWithSections => this.referencedSymbols; + + public IEnumerable ResolvedSections => this.resolvedSections; + + private bool BuildingMergeModule { get; } + + private IMessaging Messaging { get; } + + /// + /// Resolves all the simple references in a section. + /// + public void Execute() + { + this.resolvedSections = new HashSet(); + this.referencedSymbols = new HashSet(); + + this.RecursivelyResolveReferences(this.entrySection); + } + + /// + /// Recursive helper function to resolve all references of passed in section. + /// + /// Section with references to resolve. + /// Note: recursive function. + private void RecursivelyResolveReferences(IntermediateSection section) + { + // If we already resolved this section, move on to the next. + if (!this.resolvedSections.Add(section)) + { + return; + } + + // Process all of the references contained in this section using the collection of + // symbols provided. Then recursively call this method to process the + // located symbol's section. All in all this is a very simple depth-first + // search of the references per-section. + foreach (var wixSimpleReferenceRow in section.Symbols.OfType()) + { + // If we're building a Merge Module, ignore all references to the Media table + // because Merge Modules don't have Media tables. + if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media") + { + continue; + } + + // See if the symbol (and any of its duplicates) are appropriately accessible. + if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) + { + var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection); + if (accessible.Count == 1) + { + var accessibleSymbol = accessible[0]; + if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section) + { + this.RecursivelyResolveReferences(accessibleSymbol.Section); + } + } + else if (accessible.Count == 0) + { + this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); + } + else // display errors for the duplicate symbols. + { + var accessibleSymbol = accessible[0]; + var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString(); + + if (String.IsNullOrEmpty(referencingSourceLineNumber)) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name)); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber)); + } + + foreach (var accessibleDuplicate in accessible.Skip(1)) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers)); + } + } + } + else + { + this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); + } + } + } + + /// + /// Determine if the symbol and any of its duplicates are accessbile by referencing section. + /// + /// Section referencing the symbol. + /// Symbol being referenced. + /// List of symbols accessible by referencing section. + private List DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) + { + var accessibleSymbols = new List(); + + if (this.AccessibleSymbol(referencingSection, symbolWithSection)) + { + accessibleSymbols.Add(symbolWithSection); + } + + foreach (var dupe in symbolWithSection.PossiblyConflicts) + { + // don't count overridable WixActionSymbols + var symbolAction = symbolWithSection.Symbol as WixActionSymbol; + var dupeAction = dupe.Symbol as WixActionSymbol; + if (symbolAction?.Overridable != dupeAction?.Overridable) + { + continue; + } + + if (this.AccessibleSymbol(referencingSection, dupe)) + { + accessibleSymbols.Add(dupe); + } + } + + foreach (var dupe in symbolWithSection.Redundants) + { + if (this.AccessibleSymbol(referencingSection, dupe)) + { + accessibleSymbols.Add(dupe); + } + } + + return accessibleSymbols; + } + + /// + /// Determine if a single symbol is accessible by the referencing section. + /// + /// Section referencing the symbol. + /// Symbol being referenced. + /// True if symbol is accessible. + private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) + { + switch (symbolWithSection.Access) + { + case AccessModifier.Global: + return true; + case AccessModifier.Library: + return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId); + case AccessModifier.File: + return symbolWithSection.Section.CompilationId == referencingSection.CompilationId; + case AccessModifier.Section: + return referencingSection == symbolWithSection.Section; + default: + throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access)); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/SymbolWithSection.cs b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs new file mode 100644 index 00000000..08e01077 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/SymbolWithSection.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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + + /// + /// Symbol with section representing a single unique symbol. + /// + internal class SymbolWithSection + { + private HashSet possibleConflicts; + private HashSet redundants; + + /// + /// Creates a symbol for a symbol. + /// + /// + /// Symbol for the symbol + public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol) + { + this.Symbol = symbol; + this.Section = section; + this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id); + } + + /// + /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility. + /// + /// Accessbility of the symbol. + public AccessModifier Access => this.Symbol.Id.Access; + + /// + /// Gets the name of the symbol. + /// + /// Name of the symbol. + public string Name { get; } + + /// + /// Gets the symbol for this symbol. + /// + /// Symbol for this symbol. + public IntermediateSymbol Symbol { get; } + + /// + /// Gets the section for the symbol. + /// + /// Section for the symbol. + public IntermediateSection Section { get; } + + /// + /// Gets any duplicates of this symbol with sections that are possible conflicts. + /// + public IEnumerable PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty(); + + /// + /// Gets any duplicates of this symbol with sections that are redundant. + /// + public IEnumerable Redundants => this.redundants ?? Enumerable.Empty(); + + /// + /// Adds a duplicate symbol with sections that is a possible conflict. + /// + /// Symbol with section that is a possible conflict of this symbol. + public void AddPossibleConflict(SymbolWithSection symbolWithSection) + { + if (null == this.possibleConflicts) + { + this.possibleConflicts = new HashSet(); + } + + this.possibleConflicts.Add(symbolWithSection); + } + + /// + /// Adds a duplicate symbol that is redundant. + /// + /// Symbol with section that is redundant of this symbol. + public void AddRedundant(SymbolWithSection symbolWithSection) + { + if (null == this.redundants) + { + this.redundants = new HashSet(); + } + + this.redundants.Add(symbolWithSection); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs b/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs new file mode 100644 index 00000000..2b1925ad --- /dev/null +++ b/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs @@ -0,0 +1,75 @@ +// 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.Link +{ + using System; + using WixToolset.Data.Symbols; + + internal static class WixComplexReferenceSymbolExtensions + { + /// + /// Creates a shallow copy of the ComplexReference. + /// + /// A shallow copy of the ComplexReference. + public static WixComplexReferenceSymbol Clone(this WixComplexReferenceSymbol source) + { + var clone = new WixComplexReferenceSymbol(source.SourceLineNumbers, source.Id); + clone.ParentType = source.ParentType; + clone.Parent = source.Parent; + clone.ParentLanguage = source.ParentLanguage; + clone.ChildType = source.ChildType; + clone.Child = source.Child; + clone.IsPrimary = source.IsPrimary; + + return clone; + } + + /// + /// Compares two complex references without considering the primary bit. + /// + /// this + /// Complex reference to compare to. + /// Zero if the objects are equivalent, negative number if the provided object is less, positive if greater. + public static int CompareToWithoutConsideringPrimary(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol other) + { + var comparison = symbol.ChildType - other.ChildType; + if (0 == comparison) + { + comparison = String.Compare(symbol.Child, other.Child, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = symbol.ParentType - other.ParentType; + if (0 == comparison) + { + string thisParentLanguage = null == symbol.ParentLanguage ? String.Empty : symbol.ParentLanguage; + string otherParentLanguage = null == other.ParentLanguage ? String.Empty : other.ParentLanguage; + comparison = String.Compare(thisParentLanguage, otherParentLanguage, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = String.Compare(symbol.Parent, other.Parent, StringComparison.Ordinal); + } + } + } + } + + return comparison; + } + + /// + /// Changes all of the parent references to point to the passed in parent reference. + /// + /// this + /// New parent complex reference. + public static void Reparent(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol parent) + { + symbol.Parent = parent.Parent; + symbol.ParentLanguage = parent.ParentLanguage; + symbol.ParentType = parent.ParentType; + + if (!symbol.IsPrimary) + { + symbol.IsPrimary = parent.IsPrimary; + } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs new file mode 100644 index 00000000..f9de82a9 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs @@ -0,0 +1,683 @@ +// 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.Link +{ + using System; + using System.Collections.ObjectModel; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Burn; + + /// + /// Grouping and Ordering class of the WiX toolset. + /// + internal class WixGroupingOrdering + { + private readonly IMessaging Messaging; + private List groupTypes; + private List itemTypes; + private ItemCollection items; + private readonly List symbolsUsed; + private bool loaded; + + /// + /// Creates a WixGroupingOrdering object. + /// + /// Output from which to read the group and order information. + /// Handler for any error messages. + public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) + { + this.EntrySection = entrySections; + this.Messaging = messageHandler; + + this.symbolsUsed = new List(); + this.loaded = false; + } + + private IntermediateSection EntrySection { get; } + + /// + /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. + /// + /// Group types to include. + /// Item types to include. + public void UseTypes(IEnumerable groupTypes, IEnumerable itemTypes) + { + this.groupTypes = new List(groupTypes.Select(g => g.ToString())); + this.itemTypes = new List(itemTypes.Select(i => i.ToString())); + + this.items = new ItemCollection(); + this.loaded = false; + } + + /// + /// Finds all nested items under a parent group and creates new WixGroup data for them. + /// + /// The group type for the parent group to flatten. + /// The identifier of the parent group to flatten. + /// Whether to remove used group rows before returning. + public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows) + { + var parentTypeString = parentType.ToString(); + Debug.Assert(this.groupTypes.Contains(parentTypeString)); + + this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); + if (this.Messaging.EncounteredError) + { + return; + } + + this.CreateNewGroupRows(parentTypeString, parentId, orderedItems); + + if (removeUsedRows) + { + this.RemoveUsedGroupRows(); + } + } + + /// + /// Finds all items under a parent group type and creates new WixGroup data for them. + /// + /// The type of the parent group to flatten. + /// Whether to remove used group rows before returning. + public void FlattenAndRewriteGroups(ComplexReferenceParentType parentType, bool removeUsedRows) + { + var parentTypeString = parentType.ToString(); + Debug.Assert(this.groupTypes.Contains(parentTypeString)); + + this.LoadFlattenOrderGroups(); + if (this.Messaging.EncounteredError) + { + return; + } + + foreach (Item item in this.items) + { + if (parentTypeString == item.Type) + { + this.CreateOrderedList(item.Type, item.Id, out var orderedItems); + this.CreateNewGroupRows(item.Type, item.Id, orderedItems); + } + } + + if (removeUsedRows) + { + this.RemoveUsedGroupRows(); + } + } + + + /// + /// Creates a flattened and ordered list of items for the given parent group. + /// + /// The group type for the parent group to flatten. + /// The identifier of the parent group to flatten. + /// The returned list of ordered items. + private void CreateOrderedList(string parentType, string parentId, out List orderedItems) + { + orderedItems = null; + + this.LoadFlattenOrderGroups(); + if (this.Messaging.EncounteredError) + { + return; + } + + if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); + return; + } + + orderedItems = new List(parentItem.ChildItems); + orderedItems.Sort(new Item.AfterItemComparer()); + } + + /// + /// Removes rows from WixGroup that have been used by this object. + /// + public void RemoveUsedGroupRows() + { + foreach (var symbol in this.symbolsUsed) + { + this.EntrySection.RemoveSymbol(symbol); + } + } + + /// + /// Creates new WixGroup rows for a list of items. + /// + /// The group type for the parent group in the new rows. + /// The identifier of the parent group in the new rows. + /// The list of new items. + private void CreateNewGroupRows(string parentType, string parentId, List orderedItems) + { + // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither + // does WiX (although they do, currently). We probably want to "upgrade" this to a new + // table that includes a sequence number, and then change the code that uses ordered + // groups to read from that table instead. + foreach (var item in orderedItems) + { + this.EntrySection.AddSymbol(new WixGroupSymbol(item.Row.SourceLineNumbers) + { + ParentId = parentId, + ParentType = (ComplexReferenceParentType)Enum.Parse(typeof(ComplexReferenceParentType), parentType), + ChildId = item.Id, + ChildType = (ComplexReferenceChildType)Enum.Parse(typeof(ComplexReferenceChildType), item.Type), + }); + } + } + + // Group/Ordering Flattening Logic + // + // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in + // play: grouping (parent/child relationships) and ordering (before/after relationships). + // Dealing with just one or the other is straghtforward. Groups can be flattened + // recursively. Ordering can be propagated in either direction. When the ordering also + // participates in the grouping constructions, however, things get trickier. For the + // purposes of this discussion, we're dealing with "items" and "groups", and an instance + // of either of them can be marked as coming "after" some other instance. + // + // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, + // and B is after C, then we can say that A is after *both* B and C. If a group is involved, + // it acts as a proxy for all of its included items and any sub-groups. + + /// + /// Internal workhorse for ensuring that group and ordering information has + /// been loaded and applied. + /// + private void LoadFlattenOrderGroups() + { + if (!this.loaded) + { + this.LoadGroups(); + this.LoadOrdering(); + + // It would be really nice to have a "find circular after dependencies" + // function, but it gets much more complicated because of the way that + // the dependencies are propagated across group boundaries. For now, we + // just live with the dependency loop detection as we flatten the + // dependencies. Group references, however, we can check directly. + this.FindCircularGroupReferences(); + + if (!this.Messaging.EncounteredError) + { + this.FlattenGroups(); + this.FlattenOrdering(); + } + + this.loaded = true; + } + } + + /// + /// Loads data from the WixGroup table. + /// + private void LoadGroups() + { + //Table wixGroupTable = this.output.Tables["WixGroup"]; + //if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) + //{ + // // TODO: Change message name to make it *not* Bundle specific? + // this.Write(WixErrors.MissingBundleInformation("WixGroup")); + //} + + // Collect all of the groups + foreach (var symbol in this.EntrySection.Symbols.OfType()) + { + var rowParentName = symbol.ParentId; + var rowParentType = symbol.ParentType.ToString(); + var rowChildName = symbol.ChildId; + var rowChildType = symbol.ChildType.ToString(); + + // If this row specifies a parent or child type that's not in our + // lists, we assume it's not a row that we're concerned about. + if (!this.groupTypes.Contains(rowParentType) || + !this.itemTypes.Contains(rowChildType)) + { + continue; + } + + this.symbolsUsed.Add(symbol); + + if (!this.items.TryGetValue(rowParentType, rowParentName, out var parentItem)) + { + parentItem = new Item(symbol, rowParentType, rowParentName); + this.items.Add(parentItem); + } + + if (!this.items.TryGetValue(rowChildType, rowChildName, out var childItem)) + { + childItem = new Item(symbol, rowChildType, rowChildName); + this.items.Add(childItem); + } + + parentItem.ChildItems.Add(childItem); + } + } + + /// + /// Flattens group/item information. + /// + private void FlattenGroups() + { + foreach (Item item in this.items) + { + item.FlattenChildItems(); + } + } + + /// + /// Finds and reports circular references in the group/item data. + /// + private void FindCircularGroupReferences() + { + ItemCollection itemsInKnownLoops = new ItemCollection(); + foreach (Item item in this.items) + { + if (itemsInKnownLoops.Contains(item)) + { + continue; + } + + ItemCollection itemsSeen = new ItemCollection(); + string circularReference; + if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) + { + itemsInKnownLoops.Add(itemsSeen); + this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); + } + } + } + + /// + /// Recursive worker to find and report circular references in group/item data. + /// + /// The sentinal item being checked. + /// The current item in the recursion. + /// A list of all items already visited (for performance). + /// A list of items in the current circular reference, if one was found; null otherwise. + /// True if a circular reference was found; false otherwise. + private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) + { + circularReference = null; + foreach (Item subitem in currentItem.ChildItems) + { + if (checkItem == subitem) + { + // TODO: Even better would be to include the source lines for each reference! + circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", + currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); + return true; + } + + if (!itemsSeen.Contains(subitem)) + { + itemsSeen.Add(subitem); + if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) + { + // TODO: Even better would be to include the source lines for each reference! + circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", + currentItem.Type, currentItem.Id, circularReference); + return true; + } + } + } + + return false; + } + + /// + /// Loads ordering dependency data from the WixOrdering table. + /// + private void LoadOrdering() + { + //Table wixOrderingTable = output.Tables["WixOrdering"]; + //if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) + //{ + // // TODO: Do we need a message here? + // return; + //} + + foreach (var row in this.EntrySection.Symbols.OfType()) + { + var rowItemType = row.ItemType.ToString(); + var rowItemName = row.ItemIdRef; + var rowDependsOnType = row.DependsOnType.ToString(); + var rowDependsOnName = row.DependsOnIdRef; + + // If this row specifies some other (unknown) type in either + // position, we assume it's not a row that we're concerned about. + // For ordering, we allow group and item in either position. + if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || + !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) + { + continue; + } + + if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); + } + + if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); + } + + if (null == item || null == dependsOn) + { + continue; + } + + item.AddAfter(dependsOn, this.Messaging); + } + } + + /// + /// Flattens the ordering dependencies in the groups/items. + /// + private void FlattenOrdering() + { + // Because items don't know about their parent groups (and can, in fact, be + // in more than one group at a time), we need to pre-propagate the 'afters' + // from each parent item to its children before we attempt to flatten the + // ordering. + foreach (Item item in this.items) + { + item.PropagateAfterToChildItems(this.Messaging); + } + + foreach (Item item in this.items) + { + item.FlattenAfters(this.Messaging); + } + } + + /// + /// A variant of KeyedCollection that doesn't throw when an item is re-added. + /// + /// Key type for the collection. + /// Item type for the colelction. + internal abstract class EnhancedKeyCollection : KeyedCollection + { + new public void Add(TItem item) + { + if (!this.Contains(item)) + { + base.Add(item); + } + } + + public void Add(Collection list) + { + foreach (TItem item in list) + { + this.Add(item); + } + } + + public void Remove(Collection list) + { + foreach (TItem item in list) + { + this.Remove(item); + } + } + + public bool TryGetValue(TKey key, out TItem item) + { + // KeyedCollection doesn't implement the TryGetValue() method, but it's + // a useful concept. We can't just always pass this to the enclosed + // Dictionary, however, because it doesn't always exist! If it does, we + // can delegate to it as one would expect. If it doesn't, we have to + // implement everything ourselves in terms of Contains(). + + if (null != this.Dictionary) + { + return this.Dictionary.TryGetValue(key, out item); + } + + if (this.Contains(key)) + { + item = this[key]; + return true; + } + + item = default(TItem); + return false; + } + +#if DEBUG + // This just makes debugging easier... + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + foreach (TItem item in this) + { + sb.AppendFormat("{0}, ", item); + } + sb.Length -= 2; + return sb.ToString(); + } +#endif // DEBUG + } + + /// + /// A specialized EnhancedKeyCollection, typed to Items. + /// + internal class ItemCollection : EnhancedKeyCollection + { + protected override string GetKeyForItem(Item item) + { + return item.Key; + } + + public bool TryGetValue(string type, string id, out Item item) + { + return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); + } + + public static string CreateKeyFromTypeId(string type, string id) + { + return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); + } + } + + /// + /// An item (or group) in the grouping/ordering engine. + /// + /// Encapsulates nested group membership and also before/after + /// ordering dependencies. + internal class Item + { + private readonly ItemCollection afterItems; + private readonly ItemCollection beforeItems; // for checking for circular references + private bool flattenedAfterItems; + + public Item(IntermediateSymbol row, string type, string id) + { + this.Row = row; + this.Type = type; + this.Id = id; + + this.Key = ItemCollection.CreateKeyFromTypeId(type, id); + + this.afterItems = new ItemCollection(); + this.beforeItems = new ItemCollection(); + this.flattenedAfterItems = false; + } + + public IntermediateSymbol Row { get; private set; } + public string Type { get; private set; } + public string Id { get; private set; } + public string Key { get; private set; } + +#if DEBUG + // Makes debugging easier... + public override string ToString() + { + return this.Key; + } +#endif // DEBUG + + public ItemCollection ChildItems { get; } = new ItemCollection(); + + /// + /// Removes any nested groups under this item and replaces + /// them with their child items. + /// + public void FlattenChildItems() + { + ItemCollection flattenedChildItems = new ItemCollection(); + + foreach (Item childItem in this.ChildItems) + { + if (0 == childItem.ChildItems.Count) + { + flattenedChildItems.Add(childItem); + } + else + { + childItem.FlattenChildItems(); + flattenedChildItems.Add(childItem.ChildItems); + } + } + + this.ChildItems.Clear(); + this.ChildItems.Add(flattenedChildItems); + } + + /// + /// Adds a list of items to the 'after' ordering collection. + /// + /// List of items to add. + /// Message handler in case a circular ordering reference is found. + public void AddAfter(ItemCollection items, IMessaging messageHandler) + { + foreach (Item item in items) + { + this.AddAfter(item, messageHandler); + } + } + + /// + /// Adds an item to the 'after' ordering collection. + /// + /// Item to add. + /// Message handler in case a circular ordering reference is found. + public void AddAfter(Item after, IMessaging messageHandler) + { + if (this.beforeItems.Contains(after)) + { + // We could try to chain this up (the way that group circular dependencies + // are reported), but since we're in the process of flattening, we may already + // have lost some distinction between authored and propagated ordering. + string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", + this.Type, this.Id, after.Type, after.Id); + messageHandler.Write(ErrorMessages.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); + return; + } + + this.afterItems.Add(after); + after.beforeItems.Add(this); + } + + /// + /// Propagates 'after' dependencies from an item to its child items. + /// + /// Message handler in case a circular ordering reference is found. + /// Because items don't know about their parent groups (and can, in fact, be in more + /// than one group at a time), we need to propagate the 'afters' from each parent item to its children + /// before we attempt to flatten the ordering. + public void PropagateAfterToChildItems(IMessaging messageHandler) + { + if (this.ShouldItemPropagateChildOrdering()) + { + foreach (Item childItem in this.ChildItems) + { + childItem.AddAfter(this.afterItems, messageHandler); + } + } + } + + /// + /// Flattens the ordering dependency for this item. + /// + /// Message handler in case a circular ordering reference is found. + public void FlattenAfters(IMessaging messageHandler) + { + if (this.flattenedAfterItems) + { + return; + } + + this.flattenedAfterItems = true; + + // Ensure that if we're after something (A), and *it's* after something (B), + // that we list ourselved as after both (A) *and* (B). + ItemCollection nestedAfterItems = new ItemCollection(); + + foreach (Item afterItem in this.afterItems) + { + afterItem.FlattenAfters(messageHandler); + nestedAfterItems.Add(afterItem.afterItems); + + if (afterItem.ShouldItemPropagateChildOrdering()) + { + // If we are after a group, it really means + // we are after all of the group's children. + foreach (Item childItem in afterItem.ChildItems) + { + childItem.FlattenAfters(messageHandler); + nestedAfterItems.Add(childItem.afterItems); + nestedAfterItems.Add(childItem); + } + } + } + + this.AddAfter(nestedAfterItems, messageHandler); + } + + // We *don't* propagate ordering information from Packages or + // Containers to their children, because ordering doesn't matter + // for them, and a Payload in two Packages (or Containers) can + // cause a circular reference to occur. + private bool ShouldItemPropagateChildOrdering() + { + if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) || + String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal)) + { + return false; + } + return true; + } + + /// + /// Helper IComparer class to make ordering easier. + /// + internal class AfterItemComparer : IComparer + { + public int Compare(Item x, Item y) + { + if (x.afterItems.Contains(y)) + { + return 1; + } + else if (y.afterItems.Contains(x)) + { + return -1; + } + + return String.CompareOrdinal(x.Id, y.Id); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/LinkContext.cs b/src/wix/WixToolset.Core/LinkContext.cs new file mode 100644 index 00000000..b99bb9c4 --- /dev/null +++ b/src/wix/WixToolset.Core/LinkContext.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; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class LinkContext : ILinkContext + { + internal LinkContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection ExtensionData { get; set; } + + public OutputType ExpectedOutputType { get; set; } + + public IReadOnlyCollection Intermediates { get; set; } + + public ISymbolDefinitionCreator SymbolDefinitionCreator { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Linker.cs b/src/wix/WixToolset.Core/Linker.cs new file mode 100644 index 00000000..47671f26 --- /dev/null +++ b/src/wix/WixToolset.Core/Linker.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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using WixToolset.Core.Link; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Linker core of the WiX toolset. + /// + internal class Linker : ILinker + { + private static readonly string EmptyGuid = Guid.Empty.ToString("B"); + + private readonly bool sectionIdOnRows; + + /// + /// Creates a linker. + /// + internal Linker(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = this.ServiceProvider.GetService(); + this.sectionIdOnRows = true; // TODO: what is the correct value for this? + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private ILinkContext Context { get; set; } + + /// + /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. + /// + /// The path to output the xml file. + public string UnreferencedSymbolsFile { get; set; } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages { get; set; } + + /// + /// Links a collection of sections into an output. + /// + /// Output intermediate from the linking. + public Intermediate Link(ILinkContext context) + { + this.Context = context; + + if (this.Context.SymbolDefinitionCreator == null) + { + this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService(); + } + + foreach (var extension in this.Context.Extensions) + { + extension.PreLink(this.Context); + } + + var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled)); + if (invalidIntermediates.Any()) + { + this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id)))); + } + + Intermediate intermediate = null; + try + { + var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); + var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList(); + + // Add sections from the extensions with data. + foreach (var data in this.Context.ExtensionData) + { + var library = data.GetLibrary(this.Context.SymbolDefinitionCreator); + + if (library != null) + { + sections.AddRange(library.Sections); + } + } + + //this.activeOutput = null; + + var multipleFeatureComponents = new Hashtable(); + + var wixVariables = new Dictionary(); + + // First find the entry section and while processing all sections load all the symbols from all of the sections. + var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); + find.Execute(); + + // Must have found the entry section by now. + if (null == find.EntrySection) + { + if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown) + { + throw new WixException(ErrorMessages.MissingEntrySection()); + } + else + { + throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); + } + } + + // Add the missing standard action and directory symbols. + this.LoadStandardSymbols(find.SymbolsByName); + + // Resolve the symbol references to find the set of sections we care about for linking. + // Of course, we start with the entry section (that's how it got its name after all). + var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); + resolve.Execute(); + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Reset the sections to only those that were resolved then flatten the complex + // references that particpate in groups. + sections = resolve.ResolvedSections.ToList(); + + // TODO: consider filtering "localizations" down to only those localizations from + // intermediates in the sections. + + this.FlattenSectionsComplexReferences(sections); + + if (this.Messaging.EncounteredError) + { + return null; + } + + // The hard part in linking is processing the complex references. + var referencedComponents = new HashSet(); + var componentsToFeatures = new ConnectToFeatureCollection(); + var featuresToFeatures = new ConnectToFeatureCollection(); + var modulesToFeatures = new ConnectToFeatureCollection(); + this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Display an error message for Components that were not referenced by a Feature. + foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component)) + { + if (!referencedComponents.Contains(symbolWithSection.Name)) + { + this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id)); + } + } + + // Report duplicates that would ultimately end up being primary key collisions. + { + var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); + reportDupes.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + // resolve the feature to feature connects + this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); + + // Create a new section to hold the linked content. Start with the entry section's + // metadata. + var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); + + var sectionCount = 0; + + foreach (var section in sections) + { + sectionCount++; + + var sectionId = section.Id; + if (null == sectionId && this.sectionIdOnRows) + { + sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); + } + + foreach (var symbol in section.Symbols) + { + if (find.RedundantSymbols.Contains(symbol)) + { + continue; + } + + var copySymbol = true; // by default, copy symbols. + + // handle special tables + switch (symbol.Definition.Type) + { + case SymbolDefinitionType.Class: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.Extension: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.Assembly: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.PublishComponent: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.Shortcut: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.TypeLib: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.WixMerge: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null); + } + break; + + case SymbolDefinitionType.WixComplexReference: + copySymbol = false; + break; + + case SymbolDefinitionType.WixSimpleReference: + copySymbol = false; + break; + + case SymbolDefinitionType.WixVariable: + this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); + copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. + break; + } + + if (copySymbol) + { + resolvedSection.AddSymbol(symbol); + } + } + } + + // Copy the module to feature connections into the output. + foreach (ConnectToFeature connectToFeature in modulesToFeatures) + { + foreach (var feature in connectToFeature.ConnectFeatures) + { + resolvedSection.AddSymbol(new WixFeatureModulesSymbol + { + FeatureRef = feature, + WixMergeRef = connectToFeature.ChildId + }); + } + } + + // Correct the section Id in FeatureComponents table. + if (this.sectionIdOnRows) + { +#if TODO_DO_SYMBOLS_NEED_SECTIONIDS + var componentSectionIds = resolvedSection.Symbols.OfType().ToDictionary(c => c.Id.Id, c => c.SectionId); + + foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType()) + { + if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) + { + featureComponentSymbol.SectionId = componentSectionId; + } + } +#endif + } + + // Copy the wix variable rows to the output now that all overriding has been accounted for. + foreach (var symbol in wixVariables.Values) + { + resolvedSection.AddSymbol(symbol); + } + + // Bundles have groups of data that must be flattened in a way different from other types. + if (resolvedSection.Type == SectionType.Bundle) + { + var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + var collate = new CollateLocalizationsCommand(this.Messaging, localizations); + var localizationsByCulture = collate.Execute(); + + intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); + } + finally + { + foreach (var extension in this.Context.Extensions) + { + extension.PostLink(intermediate); + } + } + + return this.Messaging.EncounteredError ? null : intermediate; + } + + /// + /// Check for colliding values and collect the wix variable rows. + /// + /// Collection of WixVariableSymbols by id. + /// WixVariableSymbol to add, if not overridden. + private void AddWixVariable(Dictionary wixVariables, WixVariableSymbol symbol) + { + var id = symbol.Id.Id; + + if (wixVariables.TryGetValue(id, out var collidingSymbol)) + { + if (collidingSymbol.Overridable && !symbol.Overridable) + { + wixVariables[id] = symbol; + } + else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) + { + this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); + } + } + else + { + wixVariables.Add(id, symbol); + } + } + + /// + /// Load the standard action and directory symbols. + /// + /// Collection of symbols. + private void LoadStandardSymbols(IDictionary symbolsByName) + { + foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) + { + var symbolWithSection = new SymbolWithSection(null, actionSymbol); + + // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. + if (!symbolsByName.ContainsKey(symbolWithSection.Name)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + } + + foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) + { + var symbolWithSection = new SymbolWithSection(null, directorySymbol); + + // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. + if (!symbolsByName.ContainsKey(symbolWithSection.Name)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + } + } + + /// + /// Process the complex references. + /// + /// Active section to add symbols to. + /// Sections that are referenced during the link process. + /// Collection of all components referenced by complex reference. + /// Component to feature complex references. + /// Feature to feature complex references. + /// Module to feature complex references. + private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable sections, ISet referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) + { + var componentsToModules = new Hashtable(); + + foreach (var section in sections) + { + // Need ToList since we might want to add symbols while processing. + foreach (var wixComplexReferenceRow in section.Symbols.OfType().ToList()) + { + ConnectToFeature connection; + switch (wixComplexReferenceRow.ParentType) + { + case ComplexReferenceParentType.Feature: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.Component: + connection = componentsToFeatures[wixComplexReferenceRow.Child]; + if (null == connection) + { + componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); + } + else if (wixComplexReferenceRow.IsPrimary) + { + if (connection.IsExplicitPrimaryFeature) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), connection.PrimaryFeature ?? resolvedSection.Id)); + continue; + } + else + { + connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects + connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature + connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again + } + } + else + { + connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); + } + + // add a row to the FeatureComponents table + section.AddSymbol(new FeatureComponentsSymbol + { + FeatureRef = wixComplexReferenceRow.Parent, + ComponentRef = wixComplexReferenceRow.Child, + }); + + // index the component for finding orphaned records + var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child); + referencedComponents.Add(symbolName); + + break; + + case ComplexReferenceChildType.Feature: + connection = featuresToFeatures[wixComplexReferenceRow.Child]; + if (null != connection) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); + continue; + } + + featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); + break; + + case ComplexReferenceChildType.Module: + connection = modulesToFeatures[wixComplexReferenceRow.Child]; + if (null == connection) + { + modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); + } + else if (wixComplexReferenceRow.IsPrimary) + { + if (connection.IsExplicitPrimaryFeature) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); + continue; + } + else + { + connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects + connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature + connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again + } + } + else + { + connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); + } + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + case ComplexReferenceParentType.Module: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.Component: + if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child)) + { + this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child)); + continue; + } + else + { + componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new + + // add a row to the ModuleComponents table + section.AddSymbol(new ModuleComponentsSymbol + { + Component = wixComplexReferenceRow.Child, + ModuleID = wixComplexReferenceRow.Parent, + Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage), + }); + } + + // index the component for finding orphaned records + var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child); + referencedComponents.Add(componentSymbolName); + + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + case ComplexReferenceParentType.Patch: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.PatchFamily: + case ComplexReferenceChildType.PatchFamilyGroup: + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + case ComplexReferenceParentType.Product: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.Feature: + connection = featuresToFeatures[wixComplexReferenceRow.Child]; + if (null != connection) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); + continue; + } + + featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + default: + // Note: Groups have been processed before getting here so they are not handled by any case above. + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); + } + } + } + } + + /// + /// Flattens all complex references in all sections in the collection. + /// + /// Sections that are referenced during the link process. + private void FlattenSectionsComplexReferences(IEnumerable sections) + { + var parentGroups = new Dictionary>(); + var parentGroupsSections = new Dictionary(); + var parentGroupsNeedingProcessing = new Dictionary(); + + // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); + + // Step 1: Gather all of the complex references that are going to participate + // in the flatting process. This means complex references that have "grouping + // parents" of Features, Modules, and, of course, Groups. These references + // that participate in a "grouping parent" will be removed from their section + // now and after processing added back in Step 3 below. + foreach (var section in sections) + { + var removeSymbols = new List(); + + foreach (var symbol in section.Symbols) + { + // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, + // and Module. Non-grouping complex references are simple and + // resolved during normal complex reference resolutions. + if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow && + (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)) + { + var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent); + + // Group all complex references with a common parent + // together so we can find them quickly while processing in + // Step 2. + if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs)) + { + childrenComplexRefs = new List(); + parentGroups.Add(parentTypeAndId, childrenComplexRefs); + } + + childrenComplexRefs.Add(wixComplexReferenceRow); + removeSymbols.Add(wixComplexReferenceRow); + + // Remember the mapping from set of complex references with a common + // parent to their section. We'll need this to add them back to the + // correct section in Step 3. + if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection)) + { + parentGroupsSections.Add(parentTypeAndId, section); + } + + // If the child of the complex reference is another group, then in Step 2 + // we're going to have to process this complex reference again to copy + // the child group's references into the parent group. + if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) + { + if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) + { + parentGroupsNeedingProcessing.Add(parentTypeAndId, section); + } + } + } + } + + foreach (var removeSymbol in removeSymbols) + { + section.RemoveSymbol(removeSymbol); + } + } + + Debug.Assert(parentGroups.Count == parentGroupsSections.Count); + Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); + + // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); + + // Step 2: Loop through the parent groups that have nested groups removing + // them from the hash table as they are processed. At the end of this the + // complex references should all be flattened. + var keys = parentGroupsNeedingProcessing.Keys.ToList(); + + foreach (var key in keys) + { + if (parentGroupsNeedingProcessing.ContainsKey(key)) + { + var loopDetector = new Stack(); + this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); + } + else + { + // the group must have allready been procesed and removed from the hash table + } + } + Debug.Assert(0 == parentGroupsNeedingProcessing.Count); + + // Step 3: Finally, ensure that all of the groups that were removed + // in Step 1 and flattened in Step 2 are added to their appropriate + // section. This is where we will toss out the final no-longer-needed + // groups. + foreach (var parentGroup in parentGroups.Keys) + { + var section = parentGroupsSections[parentGroup]; + + foreach (var wixComplexReferenceRow in parentGroups[parentGroup]) + { + if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && + (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && + (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) + { + section.AddSymbol(wixComplexReferenceRow); + } + } + } + + // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); + } + + private string CombineTypeAndId(ComplexReferenceParentType type, string id) + { + return String.Concat(type.ToString(), ":", id); + } + + private string CombineTypeAndId(ComplexReferenceChildType type, string id) + { + return String.Concat(type.ToString(), ":", id); + } + + /// + /// Recursively processes the group. + /// + /// String combination type and id of group to process next. + /// Stack of groups processed thus far. Used to detect loops. + /// Hash table of complex references grouped by parent id. + /// Hash table of parent groups that still have nested groups that need to be flattened. + private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Dictionary> parentGroups, Dictionary parentGroupsNeedingProcessing) + { + Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)); + loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying + + var allNewChildComplexReferences = new List(); + + var referencesToParent = parentGroups[parentTypeAndId]; + foreach (var wixComplexReferenceRow in referencesToParent) + { + Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); + Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent)); + + // We are only interested processing when the child is a group. + if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) + { + var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child); + if (loopDetector.Contains(childTypeAndId)) + { + // Create a comma delimited list of the references that participate in the + // loop for the error message. Start at the bottom of the stack and work the + // way up to present the loop as a directed graph. + var loop = String.Join(" -> ", loopDetector); + + this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop)); + + // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the + // exit of this method does at the end because we are exiting early. + loopDetector.Pop(); + parentGroupsNeedingProcessing.Remove(parentTypeAndId); + + return; // bail + } + + // Check to see if the child group still needs to be processed. If so, + // go do that so that we'll get all of that children's (and children's + // children) complex references correctly merged into our parent group. + if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) + { + this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); + } + + // If the child is a parent to anything (i.e. the parent has grandchildren) + // clone each of the children's complex references, repoint them to the parent + // complex reference (because we're moving references up the tree), and finally + // add the cloned child's complex reference to the list of complex references + // that we'll eventually add to the parent group. + if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild)) + { + foreach (var crefChild in referencesToChild) + { + // Only merge up the non-group items since groups are purged + // after this part of the processing anyway (cloning them would + // be a complete waste of time). + if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || + (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) + { + var crefChildClone = crefChild.Clone(); + Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child); + + crefChildClone.Reparent(wixComplexReferenceRow); + allNewChildComplexReferences.Add(crefChildClone); + } + } + } + } + } + + // Add the children group's complex references to the parent + // group. Clean out any left over groups and quietly remove any + // duplicate complex references that occurred during the merge. + referencesToParent.AddRange(allNewChildComplexReferences); + referencesToParent.Sort(ComplexReferenceComparision); + for (var i = referencesToParent.Count - 1; i >= 0; --i) + { + var wixComplexReferenceRow = referencesToParent[i]; + + if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) + { + referencesToParent.RemoveAt(i); + } + else if (i > 0) + { + // Since the list is already sorted, we can find duplicates by simply + // looking at the next sibling in the list and tossing out one if they + // match. + var crefCompare = referencesToParent[i - 1]; + if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) + { + referencesToParent.RemoveAt(i); + } + } + } + + int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y) + { + var comparison = x.ChildType - y.ChildType; + if (0 == comparison) + { + comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = x.ParentType - y.ParentType; + if (0 == comparison) + { + comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal); + } + } + } + } + + return comparison; + } + + loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here + parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference + } + + /* + /// + /// Debugging method for displaying the section complex references. + /// + /// The header. + /// The sections to display. + private void DisplaySectionComplexReferences(string header, SectionCollection sections) + { + Console.WriteLine(header); + foreach (Section section in sections) + { + Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; + + foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) + { + Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); + } + } + } + */ + + /// + /// Resolves the features connected to other features in the active output. + /// + /// Feature to feature complex references. + /// All symbols loaded from the sections. + private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary allSymbols) + { + foreach (ConnectToFeature connection in featuresToFeatures) + { + var wixSimpleReferenceRow = new WixSimpleReferenceSymbol + { + Table = "Feature", + PrimaryKeys = connection.ChildId + }; + + if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol)) + { + var featureSymbol = (FeatureSymbol)symbol.Symbol; + featureSymbol.ParentFeatureRef = connection.PrimaryFeature; + } + } + } + + /// + /// Resolve features for columns that have null guid placeholders. + /// + /// Symbol to resolve. + /// Number of the column containing the connection identifier. + /// Number of the column containing the feature. + /// Connect to feature complex references. + /// Hashtable of known components under multiple features. + private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) + { + var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn); + var featureId = symbol.AsString(featureColumn); + + if (EmptyGuid == featureId) + { + var connection = connectToFeatures[connectionId]; + + if (null == connection) + { + // display an error for the component or merge module as appropriate + if (null != multipleFeatureComponents) + { + this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id)); + } + else + { + this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId)); + } + } + else + { + // check for unique, implicit, primary feature parents with multiple possible parent features + if (this.ShowPedanticMessages && + !connection.IsExplicitPrimaryFeature && + 0 < connection.ConnectFeatures.Count) + { + // display a warning for the component or merge module as approrpriate + if (null != multipleFeatureComponents) + { + if (!multipleFeatureComponents.Contains(connectionId)) + { + this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId)); + + // remember this component so only one warning is generated for it + multipleFeatureComponents[connectionId] = null; + } + } + else + { + this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId)); + } + } + + // set the feature + symbol.Set(featureColumn, connection.PrimaryFeature); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/LinkerErrors.cs b/src/wix/WixToolset.Core/LinkerErrors.cs new file mode 100644 index 00000000..7ce8c00e --- /dev/null +++ b/src/wix/WixToolset.Core/LinkerErrors.cs @@ -0,0 +1,48 @@ +// 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; + + internal static class LinkerErrors + { + public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) + { + return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); + } + + public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2) + { + return Message(sourceLineNumbers, Ids.PackageInMultipleContainers, "The Package '{0}' is referenced from multiple containers - Container '{1}' and Container '{2}'. This is not currently supported.", packageId, containerId1, containerId2); + } + + public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId) + { + return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId); + } + + public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId) + { + return Message(sourceLineNumbers, Ids.UnscheduledChainPackage, "Found orphaned Package '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", packageId); + } + + public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId) + { + return Message(sourceLineNumbers, Ids.UnscheduledRollbackBoundary, "Found orphaned RollbackBoundary '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", rollbackBoundaryId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + OrphanedPayload = 7000, + PackageInMultipleContainers = 7001, + PayloadSharedWithBA = 7002, + UnscheduledChainPackage = 7003, + UnscheduledRollbackBoundary = 7004, + } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. + } +} diff --git a/src/wix/WixToolset.Core/LinkerWarnings.cs b/src/wix/WixToolset.Core/LinkerWarnings.cs new file mode 100644 index 00000000..968fa4ea --- /dev/null +++ b/src/wix/WixToolset.Core/LinkerWarnings.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 +{ + using WixToolset.Data; + + internal static class LinkerWarnings + { + public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) + { + return Message(sourceLineNumbers, Ids.LayoutPayloadInContainer, "The layout-only Payload '{0}' is being added to Container '{1}'. It will not be extracted during layout.", payloadId, containerId); + } + + public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2) + { + return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); + } + + public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) + { + return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + LayoutPayloadInContainer = 6900, + PayloadInMultipleContainers = 6901, + UncompressedPayloadInContainer = 6902, + } // last available is 6999. 7000 is LinkerErrors. + } +} diff --git a/src/wix/WixToolset.Core/LocalizationParser.cs b/src/wix/WixToolset.Core/LocalizationParser.cs new file mode 100644 index 00000000..d6113fc6 --- /dev/null +++ b/src/wix/WixToolset.Core/LocalizationParser.cs @@ -0,0 +1,326 @@ +// 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.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class LocalizationParser : ILocalizationParser + { + public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; + private const string XmlElementName = "WixLocalization"; + + internal LocalizationParser(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public Localization ParseLocalization(string path) + { + var document = XDocument.Load(path); + return this.ParseLocalization(document); + } + + public Localization ParseLocalization(XDocument document) + { + var root = document.Root; + Localization localization = null; + + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(root); + if (LocalizationParser.XmlElementName == root.Name.LocalName) + { + if (LocalizationParser.WxlNamespace == root.Name.Namespace) + { + localization = ParseWixLocalizationElement(this.Messaging, root); + } + else // invalid or missing namespace + { + if (null == root.Name.Namespace) + { + this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName)); + } + else + { + this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName)); + } + } + } + else + { + this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName)); + } + + return localization; + } + + /// + /// Adds a WixVariableRow to a dictionary while performing the expected override checks. + /// + /// + /// Dictionary of variable rows. + /// Row to add to the variables dictionary. + private static void AddWixVariable(IMessaging messaging, IDictionary variables, BindVariable wixVariableRow) + { + if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) + { + variables[wixVariableRow.Id] = wixVariableRow; + } + else if (!wixVariableRow.Overridable) + { + messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); + } + } + + /// + /// Parses the WixLocalization element. + /// + /// + /// Element to parse. + private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node) + { + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); + int? codepage = null; + int? summaryInformationCodepage = null; + string culture = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); + break; + case "SummaryInformationCodepage": + summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); + break; + case "Culture": + culture = attrib.Value; + break; + case "Language": + // do nothing; @Language is used for locutil which can't convert Culture to lcid + break; + default: + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + break; + } + } + else + { + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + } + } + + var variables = new Dictionary(); + var localizedControls = new Dictionary(); + + foreach (var child in node.Elements()) + { + if (LocalizationParser.WxlNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "String": + LocalizationParser.ParseString(messaging, child, variables); + break; + + case "UI": + LocalizationParser.ParseUI(messaging, child, localizedControls); + break; + + default: + messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); + break; + } + } + else + { + messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); + } + } + + return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls); + } + + /// + /// Parse a localization string into a WixVariableRow. + /// + /// + /// Element to parse. + /// + private static void ParseString(IMessaging messaging, XElement node, IDictionary variables) + { + string id = null; + var overridable = false; + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); + break; + case "Overridable": + overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + case "Localizable": + ; // do nothing + break; + default: + messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); + break; + } + } + else + { + messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); + } + } + + var value = Common.GetInnerText(node); + + if (null == id) + { + messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id")); + } + else if (0 == id.Length) + { + messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0)); + } + + if (!messaging.EncounteredError) + { + var variable = new BindVariable + { + SourceLineNumbers = sourceLineNumbers, + Id = id, + Overridable = overridable, + Value = value, + }; + + LocalizationParser.AddWixVariable(messaging, variables, variable); + } + } + + /// + /// Parse a localized control. + /// + /// + /// Element to parse. + /// Dictionary of localized controls. + private static void ParseUI(IMessaging messaging, XElement node, IDictionary localizedControls) + { + string dialog = null; + string control = null; + var x = CompilerConstants.IntegerNotSet; + var y = CompilerConstants.IntegerNotSet; + var width = CompilerConstants.IntegerNotSet; + var height = CompilerConstants.IntegerNotSet; + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); + var rightToLeft = false; + var rightAligned = false; + var leftScroll = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Dialog": + dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); + break; + case "Control": + control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); + break; + case "X": + x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Y": + y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Width": + width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Height": + height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "RightToLeft": + rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + case "RightAligned": + rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + case "LeftScroll": + leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + default: + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + break; + } + } + else + { + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + } + } + + var text = Common.GetInnerText(node); + + if (String.IsNullOrEmpty(control) && (rightToLeft || rightAligned || leftScroll)) + { + if (rightToLeft) + { + messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control")); + } + + if (rightAligned) + { + messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control")); + } + + if (leftScroll) + { + messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control")); + } + } + + if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog)) + { + messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control")); + } + + if (!messaging.EncounteredError) + { + var localizedControl = new LocalizedControl(dialog, control, x, y, width, height, rightToLeft, rightAligned, leftScroll, text); + var key = localizedControl.GetKey(); + if (localizedControls.ContainsKey(key)) + { + if (String.IsNullOrEmpty(localizedControl.Control)) + { + messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog)); + } + else + { + messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control)); + } + } + else + { + localizedControls.Add(key, localizedControl); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/ParsedWixVariable.cs b/src/wix/WixToolset.Core/ParsedWixVariable.cs new file mode 100644 index 00000000..9d308b77 --- /dev/null +++ b/src/wix/WixToolset.Core/ParsedWixVariable.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 +{ + internal class ParsedWixVariable + { + public int Index { get; set; } + + public int Length { get; set; } + + public string Namespace { get; set; } + + public string Name { get; set; } + + public string Scope { get; set; } + + public string DefaultValue { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IfContext.cs b/src/wix/WixToolset.Core/Preprocess/IfContext.cs new file mode 100644 index 00000000..91173c29 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfContext.cs @@ -0,0 +1,74 @@ +// 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.Preprocess +{ + /// + /// Context for an if statement in the preprocessor. + /// + internal class IfContext + { + private bool keep; + + /// + /// Creates a default if context object, which are used for if's within an inactive preprocessor block + /// + public IfContext() + { + this.WasEverTrue = true; + this.IfState = IfState.If; + } + + /// + /// Creates an if context object. + /// + /// Flag if context is currently active. + /// Flag if context is currently true. + /// State of context to start in. + public IfContext(bool active, bool keep, IfState state) + { + this.Active = active; + this.keep = keep; + this.WasEverTrue = keep; + this.IfState = IfState.If; + } + + /// + /// Gets and sets if this if context is currently active. + /// + /// true if context is active. + public bool Active { get; set; } + + /// + /// Gets and sets if context is current true. + /// + /// true if context is currently true. + public bool IsTrue + { + get + { + return this.keep; + } + + set + { + this.keep = value; + if (this.keep) + { + this.WasEverTrue = true; + } + } + } + + /// + /// Gets if the context was ever true. + /// + /// True if context was ever true. + public bool WasEverTrue { get; private set; } + + /// + /// Gets the current state of the if context. + /// + /// Current state of context. + public IfState IfState { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs new file mode 100644 index 00000000..6b56638a --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs @@ -0,0 +1,28 @@ +// 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.Preprocess +{ + using System; + using WixToolset.Data; + + internal delegate void IfDefEventHandler(object sender, IfDefEventArgs e); + + internal class IfDefEventArgs : EventArgs + { + public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName) + { + this.SourceLineNumbers = sourceLineNumbers; + this.IsIfDef = isIfDef; + this.IsDefined = isDefined; + this.VariableName = variableName; + } + + public SourceLineNumber SourceLineNumbers { get; } + + public bool IsDefined { get; } + + public bool IsIfDef { get; } + + public string VariableName { get; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IfState.cs b/src/wix/WixToolset.Core/Preprocess/IfState.cs new file mode 100644 index 00000000..f5bb3e87 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfState.cs @@ -0,0 +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.Core.Preprocess +{ + /// + /// Current state of the if context. + /// + internal enum IfState + { + /// Context currently in unknown state. + Unknown, + + /// Context currently inside if statement. + If, + + /// Context currently inside elseif statement.. + ElseIf, + + /// Conext currently inside else statement. + Else, + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs new file mode 100644 index 00000000..3c8ff2e8 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs @@ -0,0 +1,43 @@ +// 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.Preprocess +{ + using System; + using WixToolset.Data; + + /// + /// Included file event handler delegate. + /// + /// Sender of the message. + /// Arguments for the included file event. + internal delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e); + + /// + /// Event args for included file event. + /// + internal class IncludedFileEventArgs : EventArgs + { + /// + /// Creates a new IncludedFileEventArgs. + /// + /// Source line numbers for the included file. + /// The full path of the included file. + public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName) + { + this.SourceLineNumbers = sourceLineNumbers; + this.FullName = fullName; + } + + /// + /// Gets the full path of the included file. + /// + /// The full path of the included file. + public string FullName { get; } + + /// + /// Gets the source line numbers. + /// + /// The source line numbers. + public SourceLineNumber SourceLineNumbers { get; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs new file mode 100644 index 00000000..086a0f1a --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.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.Preprocess +{ + /// + /// Enumeration for preprocessor operations in if statements. + /// + internal enum PreprocessorOperation + { + /// The and operator. + And, + + /// The or operator. + Or, + + /// The not operator. + Not + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs new file mode 100644 index 00000000..672b4b9f --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs @@ -0,0 +1,43 @@ +// 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.Preprocess +{ + using System; + using System.Xml.Linq; + + /// + /// Preprocessed output stream event handler delegate. + /// + /// Sender of the message. + /// Arguments for the preprocessed stream event. + internal delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e); + + /// + /// Event args for preprocessed stream event. + /// + internal class ProcessedStreamEventArgs : EventArgs + { + /// + /// Creates a new ProcessedStreamEventArgs. + /// + /// Source file that is preprocessed. + /// Preprocessed output document. + public ProcessedStreamEventArgs(string sourceFile, XDocument document) + { + this.SourceFile = sourceFile; + this.Document = document; + } + + /// + /// Gets the full path of the source file. + /// + /// The full path of the source file. + public string SourceFile { get; } + + /// + /// Gets the preprocessed output stream. + /// + /// The the preprocessed output stream. + public XDocument Document { get; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs new file mode 100644 index 00000000..6d159ad0 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs @@ -0,0 +1,25 @@ +// 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.Preprocess +{ + using System; + using WixToolset.Data; + + internal delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e); + + internal class ResolvedVariableEventArgs : EventArgs + { + public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue) + { + this.SourceLineNumbers = sourceLineNumbers; + this.VariableName = variableName; + this.VariableValue = variableValue; + } + + public SourceLineNumber SourceLineNumbers { get; } + + public string VariableName { get; } + + public string VariableValue { get; } + } +} diff --git a/src/wix/WixToolset.Core/PreprocessContext.cs b/src/wix/WixToolset.Core/PreprocessContext.cs new file mode 100644 index 00000000..986045ff --- /dev/null +++ b/src/wix/WixToolset.Core/PreprocessContext.cs @@ -0,0 +1,35 @@ +// 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.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class PreprocessContext : IPreprocessContext + { + internal PreprocessContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection Extensions { get; set; } + + public Platform Platform { get; set; } + + public IReadOnlyCollection IncludeSearchPaths { get; set; } + + public string SourcePath { get; set; } + + public IDictionary Variables { get; set; } + + public SourceLineNumber CurrentSourceLineNumber { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/PreprocessResult.cs b/src/wix/WixToolset.Core/PreprocessResult.cs new file mode 100644 index 00000000..83b29a90 --- /dev/null +++ b/src/wix/WixToolset.Core/PreprocessResult.cs @@ -0,0 +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.Core +{ + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Extensibility.Data; + + internal class PreprocessResult : IPreprocessResult + { + public XDocument Document { get; set; } + + public IReadOnlyCollection IncludedFiles { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocessor.cs b/src/wix/WixToolset.Core/Preprocessor.cs new file mode 100644 index 00000000..603c0e5b --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocessor.cs @@ -0,0 +1,1520 @@ +// 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.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Core.Preprocess; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Preprocessor object + /// + internal class Preprocessor : IPreprocessor + { + private static readonly Regex DefineRegex = new Regex(@"^\s*(?.+?)\s*(=\s*(?.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); + private static readonly Regex PragmaRegex = new Regex(@"^\s*(?.+?)(?[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); + + private static readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings() + { + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + XmlResolver = null, + }; + + private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() + { + ConformanceLevel = ConformanceLevel.Fragment, + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + XmlResolver = null, + }; + + internal Preprocessor(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + /// + /// Event for ifdef/ifndef directives. + /// + public event IfDefEventHandler IfDef; + + /// + /// Event for included files. + /// + public event IncludedFileEventHandler IncludedFile; + + /// + /// Event for preprocessed stream. + /// + public event ProcessedStreamEventHandler ProcessedStream; + + // + // Event for resolved variables. + // + // TOOD: Remove? + //public event ResolvedVariableEventHandler ResolvedVariable; + + /// + /// Get the source line information for the current element. The precompiler will insert + /// special source line number information for each element that it encounters. + /// + /// Element to get source line information for. + /// + /// The source line number used to author the element being processed or + /// null if the preprocessor did not process the element or the node is + /// not an element. + /// + public static SourceLineNumber GetSourceLineNumbers(XObject node) + { + return SourceLineNumber.GetFromXAnnotation(node); + } + + /// + /// Preprocesses a file. + /// + /// The preprocessing context. + /// XDocument with the postprocessed data. + public IPreprocessResult Preprocess(IPreprocessContext context) + { + var state = new ProcessingState(this.ServiceProvider, context); + + this.PreProcess(state); + + IPreprocessResult result; + using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings)) + { + result = this.Process(state, reader); + } + + this.PostProcess(state, result); + + return result; + } + + /// + /// Preprocesses a file. + /// + /// The preprocessing context. + /// XmlReader to processing the context. + /// XDocument with the postprocessed data. + public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader) + { + if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) + { + var uri = new Uri(reader.BaseURI); + context.SourcePath = uri.AbsolutePath; + } + + var state = new ProcessingState(this.ServiceProvider, context); + + this.PreProcess(state); + + var result = this.Process(state, reader); + + this.PostProcess(state, result); + + return result; + } + + /// + /// Preprocesses a file. + /// + /// The preprocessing context. + /// XmlReader to processing the context. + /// XDocument with the postprocessed data. + private IPreprocessResult Process(ProcessingState state, XmlReader reader) + { + state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR")); + + // Process the reader into the output. + IPreprocessResult result = null; + try + { + this.PreprocessReader(state, false, reader, state.Output, 0); + + // Fire event with post-processed document. + this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output)); + + if (!this.Messaging.EncounteredError) + { + result = this.ServiceProvider.GetService(); + result.Document = state.Output; + result.IncludedFiles = state.IncludedFiles; + } + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(state, reader, 0); + throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); + } + + return result; + } + + /// + /// Determins if string is an operator. + /// + /// String to check. + /// true if string is an operator. + private static bool IsOperator(string operation) + { + if (operation == null) + { + return false; + } + + operation = operation.Trim(); + if (0 == operation.Length) + { + return false; + } + + if ("=" == operation || + "!=" == operation || + "<" == operation || + "<=" == operation || + ">" == operation || + ">=" == operation || + "~=" == operation) + { + return true; + } + return false; + } + + /// + /// Determines if expression is currently inside quotes. + /// + /// Expression to evaluate. + /// Index to start searching in expression. + /// true if expression is inside in quotes. + private static bool InsideQuotes(string expression, int index) + { + if (index == -1) + { + return false; + } + + var numQuotes = 0; + var tmpIndex = 0; + while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex))) + { + numQuotes++; + tmpIndex++; + } + + // found an even number of quotes before the index, so we're not inside + if (numQuotes % 2 == 0) + { + return false; + } + + // found an odd number of quotes, so we are inside + return true; + } + + /// + /// Tests expression to see if it starts with a keyword. + /// + /// Expression to test. + /// Operation to test for. + /// true if expression starts with a keyword. + private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) + { + expression = expression.ToUpperInvariant(); + switch (operation) + { + case PreprocessorOperation.Not: + if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) + { + return true; + } + break; + case PreprocessorOperation.And: + if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) + { + return true; + } + break; + case PreprocessorOperation.Or: + if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) + { + return true; + } + break; + default: + break; + } + return false; + } + + /// + /// Processes an xml reader into an xml writer. + /// + /// + /// Specifies if reader is from an included file. + /// Reader for the source document. + /// Node where content should be added. + /// Original offset for the line numbers being processed. + private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset) + { + var currentContainer = container; + var containerStack = new Stack(); + + var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code + var ifStack = new Stack(); + + // process the reader into the writer + while (reader.Read()) + { + // update information here in case an error occurs before the next read + this.UpdateCurrentLineNumber(state, reader, offset); + + var sourceLineNumbers = state.Context.CurrentSourceLineNumber; + + // check for changes in conditional processing + if (XmlNodeType.ProcessingInstruction == reader.NodeType) + { + var ignore = false; + string name = null; + + switch (reader.LocalName) + { + case "if": + ifStack.Push(ifContext); + if (ifContext.IsTrue) + { + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If); + } + else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true + { + ifContext = new IfContext(); + } + ignore = true; + break; + + case "ifdef": + ifStack.Push(ifContext); + name = reader.Value.Trim(); + if (ifContext.IsTrue) + { + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); + } + else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true + { + ifContext = new IfContext(); + } + ignore = true; + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); + break; + + case "ifndef": + ifStack.Push(ifContext); + name = reader.Value.Trim(); + if (ifContext.IsTrue) + { + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); + } + else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true + { + ifContext = new IfContext(); + } + ignore = true; + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); + break; + + case "elseif": + if (0 == ifStack.Count) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); + } + + if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); + } + + ifContext.IfState = IfState.ElseIf; // we're now in an elseif + if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test + { + ifContext.IsTrue = this.EvaluateExpression(state, reader.Value); + } + else if (ifContext.IsTrue) + { + ifContext.IsTrue = false; + } + ignore = true; + break; + + case "else": + if (0 == ifStack.Count) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); + } + + if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); + } + + ifContext.IfState = IfState.Else; // we're now in an else + ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now + ignore = true; + break; + + case "endif": + if (0 == ifStack.Count) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); + } + + ifContext = ifStack.Pop(); + ignore = true; + break; + } + + if (ignore) // ignore this node since we just handled it above + { + continue; + } + } + + if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing + { + continue; + } + + switch (reader.NodeType) + { + case XmlNodeType.XmlDeclaration: + var document = currentContainer as XDocument; + if (null != document) + { + document.Declaration = new XDeclaration(null, null, null); + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "version": + document.Declaration.Version = reader.Value; + break; + + case "encoding": + document.Declaration.Encoding = reader.Value; + break; + + case "standalone": + document.Declaration.Standalone = reader.Value; + break; + } + } + + } + //else + //{ + // display an error? Can this happen? + //} + break; + + case XmlNodeType.ProcessingInstruction: + switch (reader.LocalName) + { + case "define": + this.PreprocessDefine(state, reader.Value); + break; + + case "error": + this.PreprocessError(state, reader.Value); + break; + + case "warning": + this.PreprocessWarning(state, reader.Value); + break; + + case "undef": + this.PreprocessUndef(state, reader.Value); + break; + + case "include": + this.UpdateCurrentLineNumber(state, reader, offset); + this.PreprocessInclude(state, reader.Value, currentContainer); + break; + + case "foreach": + this.PreprocessForeach(state, reader, currentContainer, offset); + break; + + case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); + + case "pragma": + this.PreprocessPragma(state, reader.Value, currentContainer); + break; + + default: + // unknown processing instructions are currently ignored + break; + } + break; + + case XmlNodeType.Element: + if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek()) + { + if ("Include" != reader.LocalName) + { + this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); + } + + state.IncludeNextStack.Pop(); + state.IncludeNextStack.Push(false); + break; + } + + var empty = reader.IsEmptyElement; + var ns = XNamespace.Get(reader.NamespaceURI); + var element = new XElement(ns + reader.LocalName); + currentContainer.Add(element); + + this.UpdateCurrentLineNumber(state, reader, offset); + element.AddAnnotation(sourceLineNumbers); + + while (reader.MoveToNextAttribute()) + { + var value = state.Helper.PreprocessString(state.Context, reader.Value); + + var attribNamespace = XNamespace.Get(reader.NamespaceURI); + attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; + + element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); + } + + if (!empty) + { + containerStack.Push(currentContainer); + currentContainer = element; + } + break; + + case XmlNodeType.EndElement: + if (0 < reader.Depth || !include) + { + currentContainer = containerStack.Pop(); + } + break; + + case XmlNodeType.Text: + var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value); + currentContainer.Add(postprocessedText); + break; + + case XmlNodeType.CDATA: + var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value); + currentContainer.Add(new XCData(postprocessedValue)); + break; + + default: + break; + } + } + + if (0 != ifStack.Count) + { + throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif")); + } + + // TODO: can this actually happen? + if (0 != containerStack.Count) + { + throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes")); + } + } + + /// + /// Processes an error processing instruction. + /// + /// + /// Text from source. + private void PreprocessError(ProcessingState state, string errorMessage) + { + // Resolve other variables in the error message. + errorMessage = state.Helper.PreprocessString(state.Context, errorMessage); + + throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage)); + } + + /// + /// Processes a warning processing instruction. + /// + /// + /// Text from source. + private void PreprocessWarning(ProcessingState state, string warningMessage) + { + // Resolve other variables in the warning message. + warningMessage = state.Helper.PreprocessString(state.Context, warningMessage); + + this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage)); + } + + /// + /// Processes a define processing instruction and creates the appropriate parameter. + /// + /// + /// Text from source. + private void PreprocessDefine(ProcessingState state, string originalDefine) + { + var match = DefineRegex.Match(originalDefine); + + if (!match.Success) + { + throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine)); + } + + var defineName = match.Groups["varName"].Value; + var defineValue = match.Groups["varValue"].Value; + + // strip off the optional quotes + if (1 < defineValue.Length && + ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal)) + || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal)))) + { + defineValue = defineValue.Substring(1, defineValue.Length - 2); + } + + // resolve other variables in the variable value + defineValue = state.Helper.PreprocessString(state.Context, defineValue); + + if (defineName.StartsWith("var.", StringComparison.Ordinal)) + { + state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue); + } + else + { + state.Helper.AddVariable(state.Context, defineName, defineValue); + } + } + + /// + /// Processes an undef processing instruction and creates the appropriate parameter. + /// + /// + /// Text from source. + private void PreprocessUndef(ProcessingState state, string originalDefine) + { + var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim()); + + if (name.StartsWith("var.", StringComparison.Ordinal)) + { + state.Helper.RemoveVariable(state.Context, name.Substring(4)); + } + else + { + state.Helper.RemoveVariable(state.Context, name); + } + } + + /// + /// Processes an included file. + /// + /// + /// Path to included file. + /// Parent container for included content. + private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent) + { + var sourceLineNumbers = state.Context.CurrentSourceLineNumber; + + // Preprocess variables in the path. + includePath = state.Helper.PreprocessString(state.Context, includePath); + + var includeFile = this.GetIncludeFile(state, includePath); + + if (null == includeFile) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); + } + + using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) + { + this.PushInclude(state, includeFile); + + // process the included reader into the writer + try + { + this.PreprocessReader(state, true, reader, parent, 0); + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(state, reader, 0); + throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message)); + } + + this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); + + var includedFile = this.ServiceProvider.GetService(); + includedFile.Path = includeFile; + includedFile.SourceLineNumbers = sourceLineNumbers; + + state.IncludedFiles.Add(includedFile); + + this.PopInclude(state); + } + } + + /// + /// Preprocess a foreach processing instruction. + /// + /// + /// The xml reader. + /// The container where to output processed data. + /// Offset for the line numbers. + private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset) + { + // Find the "in" token. + var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); + if (0 > indexOfInToken) + { + throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value)); + } + + // parse out the variable name + var varName = reader.Value.Substring(0, indexOfInToken).Trim(); + var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); + + // preprocess the variable values string because it might be a variable itself + varValuesString = state.Helper.PreprocessString(state.Context, varValuesString); + + var varValues = varValuesString.Split(';'); + + // go through all the empty strings + while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType) + { + } + + // get the offset of this xml fragment (for some reason its always off by 1) + var lineInfoReader = reader as IXmlLineInfo; + if (null != lineInfoReader) + { + offset += lineInfoReader.LineNumber - 1; + } + + var textReader = reader as XmlTextReader; + // dump the xml to a string (maintaining whitespace if possible) + if (null != textReader) + { + textReader.WhitespaceHandling = WhitespaceHandling.All; + } + + var fragmentBuilder = new StringBuilder(); + var nestedForeachCount = 1; + while (nestedForeachCount != 0) + { + if (reader.NodeType == XmlNodeType.ProcessingInstruction) + { + switch (reader.LocalName) + { + case "foreach": + ++nestedForeachCount; + // Output the foreach statement + fragmentBuilder.AppendFormat("", reader.Value); + break; + + case "endforeach": + --nestedForeachCount; + if (0 != nestedForeachCount) + { + fragmentBuilder.Append(""); + } + break; + + default: + fragmentBuilder.AppendFormat("", reader.LocalName, reader.Value); + break; + } + } + else if (reader.NodeType == XmlNodeType.Element) + { + fragmentBuilder.Append(reader.ReadOuterXml()); + continue; + } + else if (reader.NodeType == XmlNodeType.Whitespace) + { + // Or output the whitespace + fragmentBuilder.Append(reader.Value); + } + else if (reader.NodeType == XmlNodeType.None) + { + throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber)); + } + + reader.Read(); + } + + using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) + { + // process each iteration, updating the variable's value each time + foreach (var varValue in varValues) + { + using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) + { + // Always overwrite foreach variables. + state.Helper.AddVariable(state.Context, varName, varValue, false); + + try + { + this.PreprocessReader(state, false, loopReader, container, offset); + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(state, loopReader, offset); + throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); + } + + fragmentStream.Position = 0; // seek back to the beginning for the next loop. + } + } + } + } + + /// + /// Processes a pragma processing instruction + /// + /// + /// Text from source. + /// + private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent) + { + var match = PragmaRegex.Match(pragmaText); + + if (!match.Success) + { + throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText)); + } + + // resolve other variables in the pragma argument(s) + var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim(); + + try + { + state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); + } + catch (Exception e) + { + throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message)); + } + } + + /// + /// Gets the next token in an expression. + /// + /// + /// Expression to parse. + /// Expression with token removed. + /// Flag if token is a string literal instead of a variable. + /// Next token. + private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral) + { + stringLiteral = false; + var token = String.Empty; + expression = expression.Trim(); + if (0 == expression.Length) + { + return String.Empty; + } + + if (expression.StartsWith("\"", StringComparison.Ordinal)) + { + stringLiteral = true; + var endingQuotes = expression.IndexOf('\"', 1); + if (-1 == endingQuotes) + { + throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // cut the quotes off the string + token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1)); + + // advance past this string + expression = expression.Substring(endingQuotes + 1).Trim(); + } + else if (expression.StartsWith("$(", StringComparison.Ordinal)) + { + // Find the ending paren of the expression + var endingParen = -1; + var openedCount = 1; + for (var i = 2; i < expression.Length; i++) + { + if ('(' == expression[i]) + { + openedCount++; + } + else if (')' == expression[i]) + { + openedCount--; + } + + if (openedCount == 0) + { + endingParen = i; + break; + } + } + + if (-1 == endingParen) + { + throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + token = expression.Substring(0, endingParen + 1); + + // Advance past this variable + expression = expression.Substring(endingParen + 1).Trim(); + } + else + { + // Cut the token off at the next equal, space, inequality operator, + // or end of string, whichever comes first + var space = expression.IndexOf(" ", StringComparison.Ordinal); + var equals = expression.IndexOf("=", StringComparison.Ordinal); + var lessThan = expression.IndexOf("<", StringComparison.Ordinal); + var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal); + var greaterThan = expression.IndexOf(">", StringComparison.Ordinal); + var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal); + var notEquals = expression.IndexOf("!=", StringComparison.Ordinal); + var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal); + int closingIndex; + + if (space == -1) + { + space = Int32.MaxValue; + } + + if (equals == -1) + { + equals = Int32.MaxValue; + } + + if (lessThan == -1) + { + lessThan = Int32.MaxValue; + } + + if (lessThanEquals == -1) + { + lessThanEquals = Int32.MaxValue; + } + + if (greaterThan == -1) + { + greaterThan = Int32.MaxValue; + } + + if (greaterThanEquals == -1) + { + greaterThanEquals = Int32.MaxValue; + } + + if (notEquals == -1) + { + notEquals = Int32.MaxValue; + } + + if (equalsNoCase == -1) + { + equalsNoCase = Int32.MaxValue; + } + + closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals))))))); + + if (Int32.MaxValue == closingIndex) + { + closingIndex = expression.Length; + } + + // If the index is 0, we hit an operator, so return it + if (0 == closingIndex) + { + // Length 2 operators + if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase) + { + closingIndex = 2; + } + else // Length 1 operators + { + closingIndex = 1; + } + } + + // Cut out the new token + token = expression.Substring(0, closingIndex).Trim(); + expression = expression.Substring(closingIndex).Trim(); + } + + return token; + } + + /// + /// Gets the value for a variable. + /// + /// + /// Original expression for error message. + /// Variable to evaluate. + /// Value of variable. + private string EvaluateVariable(ProcessingState state, string originalExpression, string variable) + { + // By default it's a literal and will only be evaluated if it + // matches the variable format + var varValue = variable; + + if (variable.StartsWith("$(", StringComparison.Ordinal)) + { + try + { + varValue = state.Helper.PreprocessString(state.Context, variable); + } + catch (ArgumentNullException) + { + // non-existent variables are expected + varValue = null; + } + } + else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) + { + // make sure it doesn't contain parenthesis + throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) + { + // shouldn't contain quotes + throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + return varValue; + } + + /// + /// Gets the left side value, operator, and right side value of an expression. + /// + /// + /// Original expression to evaluate. + /// Expression modified while processing. + /// Left side value from expression. + /// Operation in expression. + /// Right side value from expression. + private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) + { + leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral); + + // If it wasn't a string literal, evaluate it + if (!stringLiteral) + { + leftValue = this.EvaluateVariable(state, originalExpression, leftValue); + } + + // Get the operation + operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); + if (IsOperator(operation)) + { + if (stringLiteral) + { + throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); + + // If it wasn't a string literal, evaluate it + if (!stringLiteral) + { + rightValue = this.EvaluateVariable(state, originalExpression, rightValue); + } + } + else + { + // Prepend the token back on the expression since it wasn't an operator + // and put the quotes back on the literal if necessary + + if (stringLiteral) + { + operation = "\"" + operation + "\""; + } + expression = (operation + " " + expression).Trim(); + + // If no operator, just check for existence + operation = ""; + rightValue = ""; + } + } + + /// + /// Evaluates an expression. + /// + /// + /// Original expression to evaluate. + /// Expression modified while processing. + /// true if expression evaluates to true. + private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression) + { + // Quick test to see if the first token is a variable + var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); + this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue); + + var expressionValue = false; + + // If the variables don't exist, they were evaluated to null + if (null == leftValue || null == rightValue) + { + if (operation.Length > 0) + { + throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // false expression + } + else if (operation.Length == 0) + { + // There is no right side of the equation. + // If the variable was evaluated, it exists, so the expression is true + if (startsWithVariable) + { + expressionValue = true; + } + else + { + throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression)); + } + } + else + { + leftValue = leftValue.Trim(); + rightValue = rightValue.Trim(); + if ("=" == operation) + { + if (leftValue == rightValue) + { + expressionValue = true; + } + } + else if ("!=" == operation) + { + if (leftValue != rightValue) + { + expressionValue = true; + } + } + else if ("~=" == operation) + { + if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase)) + { + expressionValue = true; + } + } + else + { + // Convert the numbers from strings + int rightInt; + int leftInt; + try + { + rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture); + leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // Compare the numbers + if ("<" == operation && leftInt < rightInt || + "<=" == operation && leftInt <= rightInt || + ">" == operation && leftInt > rightInt || + ">=" == operation && leftInt >= rightInt) + { + expressionValue = true; + } + } + } + + return expressionValue; + } + + /// + /// Gets a sub-expression in parenthesis. + /// + /// + /// Original expression to evaluate. + /// Expression modified while processing. + /// Index of end of sub-expression. + /// Sub-expression in parenthesis. + private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression) + { + endSubExpression = 0; + + // if the expression doesn't start with parenthesis, leave it alone + if (!expression.StartsWith("(", StringComparison.Ordinal)) + { + return expression; + } + + // search for the end of the expression with the matching paren + var openParenIndex = 0; + var closeParenIndex = 1; + while (openParenIndex != -1 && openParenIndex < closeParenIndex) + { + closeParenIndex = expression.IndexOf(')', closeParenIndex); + if (closeParenIndex == -1) + { + throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + if (InsideQuotes(expression, closeParenIndex)) + { + // ignore stuff inside quotes (it's a string literal) + } + else + { + // Look to see if there is another open paren before the close paren + // and skip over the open parens while they are in a string literal + do + { + openParenIndex++; + openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex); + } + while (InsideQuotes(expression, openParenIndex)); + } + + // Advance past the closing paren + closeParenIndex++; + } + + endSubExpression = closeParenIndex; + + // Return the expression minus the parenthesis + return expression.Substring(1, closeParenIndex - 2); + } + + /// + /// Updates expression based on operation. + /// + /// + /// State to update. + /// Operation to apply to current value. + /// Previous result. + private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult) + { + switch (operation) + { + case PreprocessorOperation.And: + currentValue = currentValue && prevResult; + break; + case PreprocessorOperation.Or: + currentValue = currentValue || prevResult; + break; + case PreprocessorOperation.Not: + currentValue = !currentValue; + break; + default: + throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString())); + } + } + + /// + /// Evaluate an expression. + /// + /// + /// Expression to evaluate. + /// Boolean result of expression. + private bool EvaluateExpression(ProcessingState state, string expression) + { + var tmpExpression = expression; + return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true); + } + + /// + /// Recurse through the expression to evaluate if it is true or false. + /// The expression is evaluated left to right. + /// The expression is case-sensitive (converted to upper case) with the + /// following exceptions: variable names and keywords (and, not, or). + /// Comparisons with = and != are string comparisons. + /// Comparisons with inequality operators must be done on valid integers. + /// + /// The operator precedence is: + /// "" + /// () + /// <, >, <=, >=, =, != + /// Not + /// And, Or + /// + /// Valid expressions include: + /// not $(var.B) or not $(var.C) + /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A)) + /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C) + /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir) + /// $(var.A) and $(var.B)>2 or $(var.B) <= 2 + /// $(var.A) != "2" + /// + /// + /// The original expression + /// The expression currently being evaluated + /// The operation to apply to this result + /// The previous result to apply to this result + /// Boolean to indicate if the expression is true or false + private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) + { + var expressionValue = false; + expression = expression.Trim(); + if (expression.Length == 0) + { + throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // If the expression starts with parenthesis, evaluate it + if (expression.IndexOf('(') == 0) + { + var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex); + expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true); + + // Now get the rest of the expression that hasn't been evaluated + expression = expression.Substring(endSubExpressionIndex).Trim(); + } + else + { + // Check for NOT + if (StartsWithKeyword(expression, PreprocessorOperation.Not)) + { + expression = expression.Substring(3).Trim(); + if (expression.Length == 0) + { + throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true); + } + else // Expect a literal + { + expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression); + + // Expect the literal that was just evaluated to already be cut off + } + } + this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult); + + // If there's still an expression left, it must start with AND or OR. + if (expression.Trim().Length > 0) + { + if (StartsWithKeyword(expression, PreprocessorOperation.And)) + { + expression = expression.Substring(3); + return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue); + } + else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) + { + expression = expression.Substring(2); + return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); + } + else + { + throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression)); + } + } + + return expressionValue; + } + + /// + /// Update the current line number with the reader's current state. + /// + /// + /// The xml reader for the preprocessor. + /// This is the artificial offset of the line numbers from the reader. Used for the foreach processing. + private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset) + { + var lineInfoReader = reader as IXmlLineInfo; + if (null != lineInfoReader) + { + var newLine = lineInfoReader.LineNumber + offset; + + if (state.Context.CurrentSourceLineNumber.LineNumber != newLine) + { + state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine); + } + } + } + + /// + /// Pushes a file name on the stack of included files. + /// + /// + /// Name to push on to the stack of included files. + private void PushInclude(ProcessingState state, string fileName) + { + if (1023 < state.CurrentFileStack.Count) + { + throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count)); + } + + var path = Path.GetFullPath(fileName); + + state.CurrentFileStack.Push(path); + state.SourceStack.Push(state.Context.CurrentSourceLineNumber); + state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber); + state.IncludeNextStack.Push(true); + } + + /// + /// Pops a file name from the stack of included files. + /// + private void PopInclude(ProcessingState state) + { + state.Context.CurrentSourceLineNumber = state.SourceStack.Pop(); + + state.CurrentFileStack.Pop(); + state.IncludeNextStack.Pop(); + } + + /// + /// Go through search paths, looking for a matching include file. + /// Start the search in the directory of the source file, then go + /// through the search paths in the order given on the command line + /// (leftmost first, ...). + /// + /// + /// User-specified path to the included file (usually just the file name). + /// Returns a FileInfo for the found include file, or null if the file cannot be found. + private string GetIncludeFile(ProcessingState state, string includePath) + { + string finalIncludePath = null; + + includePath = includePath.Trim(); + + // remove quotes (only if they match) + if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) || + (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal))) + { + includePath = includePath.Substring(1, includePath.Length - 2); + } + + // check if the include file is a full path + if (Path.IsPathRooted(includePath)) + { + if (File.Exists(includePath)) + { + finalIncludePath = includePath; + } + } + else // relative path + { + // build a string to test the directory containing the source file first + var currentFolder = state.CurrentFileStack.Peek(); + var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath); + + // test the source file directory + if (File.Exists(includeTestPath)) + { + finalIncludePath = includeTestPath; + } + else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line + { + foreach (var includeSearchPath in state.Context.IncludeSearchPaths) + { + // if the path exists, we have found the final string + includeTestPath = Path.Combine(includeSearchPath, includePath); + if (File.Exists(includeTestPath)) + { + finalIncludePath = includeTestPath; + break; + } + } + } + } + + return finalIncludePath; + } + + private void PreProcess(ProcessingState state) + { + if (state.Context.Extensions == null) + { + return; + } + + foreach (var extension in state.Context.Extensions) + { + if (extension.Prefixes != null) + { + foreach (var prefix in extension.Prefixes) + { + if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) + { + state.ExtensionsByPrefix.Add(prefix, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); + } + } + } + + extension.PrePreprocess(state.Context); + } + } + + private void PostProcess(ProcessingState state, IPreprocessResult result) + { + if (state.Context.Extensions == null) + { + return; + } + + foreach (var extension in state.Context.Extensions) + { + extension.PostPreprocess(result); + } + } + + private class ProcessingState + { + public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context) + { + var path = Path.GetFullPath(context.SourcePath); + + this.Context = context; + this.Context.CurrentSourceLineNumber = new SourceLineNumber(path); + this.Context.Variables = this.Context.Variables == null ? new Dictionary() : new Dictionary(this.Context.Variables); + + this.Helper = serviceProvider.GetService(); + } + + public IPreprocessContext Context { get; } + + public IPreprocessHelper Helper { get; } + + public List IncludedFiles { get; } = new List(); + + public XDocument Output { get; } = new XDocument(); + + public Stack CurrentFileStack { get; } = new Stack(); + + public Dictionary ExtensionsByPrefix { get; } = new Dictionary(); + + public Stack IncludeNextStack { get; } = new Stack(); + + public Stack SourceStack { get; } = new Stack(); + } + } +} diff --git a/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs b/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..81274e3f --- /dev/null +++ b/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +// 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. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] diff --git a/src/wix/WixToolset.Core/ResolveContext.cs b/src/wix/WixToolset.Core/ResolveContext.cs new file mode 100644 index 00000000..638c8079 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveContext.cs @@ -0,0 +1,42 @@ +// 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.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ResolveContext : IResolveContext + { + internal ResolveContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection BindPaths { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection ExtensionData { get; set; } + + public IReadOnlyCollection FilterCultures { get; set; } + + public string IntermediateFolder { get; set; } + + public Intermediate IntermediateRepresentation { get; set; } + + public IReadOnlyCollection Localizations { get; set; } + + public IVariableResolver VariableResolver { get; set; } + + public bool AllowUnresolvedVariables { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ResolveFileResult.cs b/src/wix/WixToolset.Core/ResolveFileResult.cs new file mode 100644 index 00000000..f6e201d4 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveFileResult.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 +{ + using System.Collections.Generic; + using WixToolset.Extensibility.Data; + + internal class ResolveFileResult : IResolveFileResult + { + public string Path { get; set; } + + public IReadOnlyCollection CheckedPaths { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ResolveResult.cs b/src/wix/WixToolset.Core/ResolveResult.cs new file mode 100644 index 00000000..fa8e09b7 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveResult.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.Data; + + internal class ResolveResult : IResolveResult + { + public int? Codepage { get; set; } + + public int? SummaryInformationCodepage { get; set; } + + public int? PackageLcid { get; set; } + + public IReadOnlyCollection DelayedFields { get; set; } + + public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } + + public Intermediate IntermediateRepresentation { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ResolvedCabinet.cs b/src/wix/WixToolset.Core/ResolvedCabinet.cs new file mode 100644 index 00000000..be04831f --- /dev/null +++ b/src/wix/WixToolset.Core/ResolvedCabinet.cs @@ -0,0 +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.Core +{ + using WixToolset.Extensibility.Data; + + /// + /// Data returned from build file manager ResolveCabinet callback. + /// + internal class ResolvedCabinet : IResolvedCabinet + { + /// + /// Gets or sets the build option for the resolved cabinet. + /// + public CabinetBuildOption BuildOption { get; set; } + + /// + /// Gets or sets the path for the resolved cabinet. + /// + public string Path { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Resolver.cs b/src/wix/WixToolset.Core/Resolver.cs new file mode 100644 index 00000000..e93f8e1b --- /dev/null +++ b/src/wix/WixToolset.Core/Resolver.cs @@ -0,0 +1,304 @@ +// 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.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Resolver for the WiX toolset. + /// + internal class Resolver : IResolver + { + internal Resolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = serviceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + public IResolveResult Resolve(IResolveContext context) + { + foreach (var extension in context.Extensions) + { + extension.PreResolve(context); + } + + ResolveResult resolveResult = null; + try + { + var filteredLocalizations = FilterLocalizations(context); + + var variableResolver = this.CreateVariableResolver(context, filteredLocalizations); + + this.LocalizeUI(variableResolver, context.IntermediateRepresentation); + + resolveResult = this.DoResolve(context, variableResolver); + + var primaryLocalization = filteredLocalizations.FirstOrDefault(); + + if (primaryLocalization != null) + { + this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo); + + resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; + + resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; + + resolveResult.PackageLcid = cultureInfo?.LCID; + } + } + finally + { + foreach (var extension in context.Extensions) + { + extension.PostResolve(resolveResult); + } + } + + return resolveResult; + } + + private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) + { + var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); + + var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); + + IReadOnlyCollection delayedFields; + { + var command = new ResolveFieldsCommand(); + command.Messaging = this.Messaging; + command.BuildingPatch = buildingPatch; + command.VariableResolver = variableResolver; + command.BindPaths = context.BindPaths; + command.Extensions = context.Extensions; + command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + command.IntermediateFolder = context.IntermediateFolder; + command.Intermediate = context.IntermediateRepresentation; + command.SupportDelayedResolution = true; + command.AllowUnresolvedVariables = context.AllowUnresolvedVariables; + command.Execute(); + + delayedFields = command.DelayedFields; + } + +#if TODO_PATCHING + if (context.IntermediateRepresentation.SubStorages != null) + { + foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) + { + var command = new ResolveFieldsCommand(); + command.BuildingPatch = buildingPatch; + command.BindVariableResolver = context.WixVariableResolver; + command.BindPaths = context.BindPaths; + command.Extensions = context.Extensions; + command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + command.IntermediateFolder = context.IntermediateFolder; + command.Intermediate = context.IntermediateRepresentation; + command.SupportDelayedResolution = false; + command.Execute(); + } + } +#endif + + var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); + + context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); + + return new ResolveResult + { + ExpectedEmbeddedFiles = expectedEmbeddedFiles, + DelayedFields = delayedFields, + IntermediateRepresentation = context.IntermediateRepresentation + }; + } + + /// + /// Localize dialogs and controls. + /// + private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate) + { + foreach (var section in intermediate.Sections) + { + foreach (var symbol in section.Symbols.OfType()) + { + if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + symbol.HCentering = localizedControl.X; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + symbol.VCentering = localizedControl.Y; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + symbol.Width = localizedControl.Width; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + symbol.Height = localizedControl.Height; + } + + symbol.RightAligned |= localizedControl.RightAligned; + symbol.RightToLeft |= localizedControl.RightToLeft; + symbol.LeftScroll |= localizedControl.LeftScroll; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + symbol.Title = localizedControl.Text; + } + } + } + + foreach (var symbol in section.Symbols.OfType()) + { + if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + symbol.X = localizedControl.X; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + symbol.Y = localizedControl.Y; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + symbol.Width = localizedControl.Width; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + symbol.Height = localizedControl.Height; + } + + symbol.RightAligned |= localizedControl.RightAligned; + symbol.RightToLeft |= localizedControl.RightToLeft; + symbol.LeftScroll |= localizedControl.LeftScroll; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + symbol.Text = localizedControl.Text; + } + } + } + } + } + + private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable filteredLocalizations) + { + var variableResolver = this.ServiceProvider.GetService(); + + foreach (var localization in filteredLocalizations) + { + variableResolver.AddLocalization(localization); + } + + // Gather all the wix variables. + var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType(); + foreach (var symbol in wixVariableSymbols) + { + variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); + } + + return variableResolver; + } + + private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo) + { + cultureInfo = null; + + if (!String.IsNullOrEmpty(culture)) + { + try + { + cultureInfo = new CultureInfo(culture, useUserOverride: false); + } + catch + { + this.Messaging.Write(""); + } + } + + return cultureInfo != null; + } + + private static IEnumerable FilterLocalizations(IResolveContext context) + { + var result = new List(); + var filter = CalculateCultureFilter(context); + + var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList(); + + AddFilteredLocalizations(result, filter, localizations); + + // Filter localizations provided by extensions with data. + var creator = context.ServiceProvider.GetService(); + + foreach (var data in context.ExtensionData) + { + var library = data.GetLibrary(creator); + + if (library?.Localizations != null && library.Localizations.Any()) + { + var extensionFilter = (!filter.Any() && data.DefaultCulture != null) ? new[] { data.DefaultCulture } : filter; + + AddFilteredLocalizations(result, extensionFilter, library.Localizations); + } + } + + return result; + } + + private static IEnumerable CalculateCultureFilter(IResolveContext context) + { + var filter = context.FilterCultures ?? Array.Empty(); + + // If no filter was specified, look for a language neutral localization file specified + // from the command-line (not embedded in the intermediate). If found, filter on language + // neutral. + if (!filter.Any() && context.Localizations.Any(l => String.IsNullOrEmpty(l.Culture))) + { + filter = new[] { String.Empty }; + } + + return filter; + } + + private static void AddFilteredLocalizations(List result, IEnumerable filter, IEnumerable localizations) + { + // If there is no filter, return all localizations. + if (!filter.Any()) + { + result.AddRange(localizations); + } + else // filter localizations in order specified by the filter + { + foreach (var culture in filter) + { + result.AddRange(localizations.Where(l => culture.Equals(l.Culture, StringComparison.OrdinalIgnoreCase) || String.IsNullOrEmpty(l.Culture))); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/SourceFile.cs b/src/wix/WixToolset.Core/SourceFile.cs new file mode 100644 index 00000000..d7ea7a50 --- /dev/null +++ b/src/wix/WixToolset.Core/SourceFile.cs @@ -0,0 +1,17 @@ +// 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 +{ + internal class SourceFile + { + public SourceFile(string sourcePath, string outputPath) + { + this.SourcePath = sourcePath; + this.OutputPath = outputPath; + } + + public string OutputPath { get; } + + public string SourcePath { get; } + } +} diff --git a/src/wix/WixToolset.Core/UnbindContext.cs b/src/wix/WixToolset.Core/UnbindContext.cs new file mode 100644 index 00000000..c3817a08 --- /dev/null +++ b/src/wix/WixToolset.Core/UnbindContext.cs @@ -0,0 +1,29 @@ +// 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 WixToolset.Extensibility.Data; + + internal class UnbindContext : IUnbindContext + { + internal UnbindContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + 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/wix/WixToolset.Core/Unbinder.cs b/src/wix/WixToolset.Core/Unbinder.cs new file mode 100644 index 00000000..3ef77083 --- /dev/null +++ b/src/wix/WixToolset.Core/Unbinder.cs @@ -0,0 +1,99 @@ +// 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.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Unbinder core of the WiX toolset. + /// + internal sealed class Unbinder : IUnbinder + { + public Unbinder(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + var extensionManager = this.ServiceProvider.GetService(); + this.BackendFactories = extensionManager.GetServices(); + } + + public IServiceProvider ServiceProvider { get; } + + 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; set; } + + /// + /// Gets or sets the option to suppress demodularizing values. + /// + /// The option to suppress demodularizing values. + public bool SuppressDemodularization { get; set; } + + /// + /// Gets or sets the option to suppress extracting cabinets. + /// + /// The option to suppress extracting cabinets. + 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 => Path.GetTempPath(); + + /// + /// Unbind a Windows Installer file. + /// + /// The Windows Installer file. + /// The type of output to create. + /// The path where files should be exported. + /// The output representing the database. + public Intermediate Unbind(string file, OutputType outputType, string exportBasePath) + { + if (!File.Exists(file)) + { + if (OutputType.Transform == outputType) + { + throw new WixException(ErrorMessages.FileNotFound(null, file, "Transform")); + } + else + { + throw new WixException(ErrorMessages.FileNotFound(null, file, "Database")); + } + } + + // if we don't have the temporary files object yet, get one + Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there + + var context = new UnbindContext(this.ServiceProvider); + context.InputFilePath = file; + context.ExportBasePath = exportBasePath; + context.IntermediateFolder = this.TempFilesLocation; + context.IsAdminImage = this.IsAdminImage; + context.SuppressDemodularization = this.SuppressDemodularization; + context.SuppressExtractCabinets = this.SuppressExtractCabinets; + + foreach (var factory in this.BackendFactories) + { + if (factory.TryCreateBackend(outputType.ToString(), file, out var backend)) + { + return backend.Unbind(context); + } + } + + // TODO: Display message that could not find a unbinder for output type? + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/VariableResolution.cs b/src/wix/WixToolset.Core/VariableResolution.cs new file mode 100644 index 00000000..3b34e294 --- /dev/null +++ b/src/wix/WixToolset.Core/VariableResolution.cs @@ -0,0 +1,29 @@ +// 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.Extensibility.Services; + + internal class VariableResolution : IVariableResolution + { + /// + /// Indicates whether the variable should be delay resolved. + /// + public bool DelayedResolve { get; set; } + + /// + /// Indicates whether the value is the default value of the variable. + /// + public bool IsDefault { get; set; } + + /// + /// Indicates whether the value changed. + /// + public bool UpdatedValue { get; set; } + + /// + /// Resolved value. + /// + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/wix/WixToolset.Core/VariableResolver.cs b/src/wix/WixToolset.Core/VariableResolver.cs new file mode 100644 index 00000000..437cabb7 --- /dev/null +++ b/src/wix/WixToolset.Core/VariableResolver.cs @@ -0,0 +1,197 @@ +// 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.Collections.Generic; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility.Services; + + /// + /// WiX variable resolver. + /// + internal class VariableResolver : IVariableResolver + { + private readonly Dictionary locVariables; + private readonly Dictionary wixVariables; + private readonly Dictionary localizedControls; + + /// + /// Instantiate a new VariableResolver. + /// + internal VariableResolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + + this.locVariables = new Dictionary(); + this.wixVariables = new Dictionary(); + this.localizedControls = new Dictionary(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + public int VariableCount => this.wixVariables.Count; + + public void AddLocalization(Localization localization) + { + foreach (var variable in localization.Variables) + { + if (!TryAddWixVariable(this.locVariables, variable)) + { + this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id)); + } + } + + foreach (KeyValuePair localizedControl in localization.LocalizedControls) + { + if (!this.localizedControls.ContainsKey(localizedControl.Key)) + { + this.localizedControls.Add(localizedControl.Key, localizedControl.Value); + } + } + } + + public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable) + { + var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber }; + + if (!TryAddWixVariable(this.wixVariables, bindVariable)) + { + this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name)); + } + } + + public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value) + { + return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true); + } + + public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) + { + var key = LocalizedControl.GetKey(dialog, control); + return this.localizedControls.TryGetValue(key, out localizedControl); + } + + public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) + { + var start = 0; + var defaulted = true; + var delayed = false; + var updated = false; + + while (Common.TryParseWixVariable(value, start, out var parsed)) + { + var variableNamespace = parsed.Namespace; + var variableId = parsed.Name; + var variableDefaultValue = parsed.DefaultValue; + + // check for an escape sequence of !! indicating the match is not a variable expression + if (0 < parsed.Index && '!' == value[parsed.Index - 1]) + { + var sb = new StringBuilder(value); + sb.Remove(parsed.Index - 1, 1); + value = sb.ToString(); + + updated = true; + start = parsed.Index + parsed.Length - 1; + + continue; + } + + string resolvedValue = null; + + if ("loc" == variableNamespace) + { + // localization variables do not support inline default values + if (variableDefaultValue != null) + { + this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); + continue; + } + + if (this.locVariables.TryGetValue(variableId, out var bindVariable)) + { + resolvedValue = bindVariable.Value; + } + } + else if ("wix" == variableNamespace) + { + if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) + { + resolvedValue = bindVariable.Value ?? String.Empty; + defaulted = false; + } + else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified + { + resolvedValue = variableDefaultValue; + } + } + + if ("bind" == variableNamespace) + { + // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort. + delayed = true; + start = parsed.Index + parsed.Length - 1; + } + else + { + // insert the resolved value if it was found or display an error + if (null != resolvedValue) + { + if (parsed.Index == 0 && parsed.Length == value.Length) + { + value = resolvedValue; + } + else + { + var sb = new StringBuilder(value); + sb.Remove(parsed.Index, parsed.Length); + sb.Insert(parsed.Index, resolvedValue); + value = sb.ToString(); + } + + updated = true; + start = parsed.Index; + } + else + { + if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable + { + this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); + } + else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable + { + this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); + } + + start = parsed.Index + parsed.Length; + } + } + } + + return new VariableResolution + { + DelayedResolve = delayed, + IsDefault = defaulted, + UpdatedValue = updated, + Value = value, + }; + } + + private static bool TryAddWixVariable(IDictionary variables, BindVariable variable) + { + if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable)) + { + variables[variable.Id] = variable; + return true; + } + + return variable.Overridable; + } + } +} diff --git a/src/wix/WixToolset.Core/WixToolset.Core.csproj b/src/wix/WixToolset.Core/WixToolset.Core.csproj new file mode 100644 index 00000000..7242d500 --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolset.Core.csproj @@ -0,0 +1,47 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Core + WiX Toolset Core + embedded + true + true + true + + + + + <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject b/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject new file mode 100644 index 00000000..c6001ebe --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + ..\..\version.json + + + \ No newline at end of file diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs new file mode 100644 index 00000000..5d700ba0 --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs @@ -0,0 +1,117 @@ +// 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.Collections.Generic; + using WixToolset.Core.CommandLine; + using WixToolset.Core.ExtensibilityServices; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class WixToolsetServiceProvider : IWixToolsetCoreServiceProvider + { + public WixToolsetServiceProvider() + { + this.CreationFunctions = new Dictionary, object>>(); + this.Singletons = new Dictionary(); + + // Singletons. + this.AddService((provider, singletons) => AddSingleton(singletons, new ExtensionManager(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new Messaging())); + this.AddService((provider, singletons) => AddSingleton(singletons, new SymbolDefinitionCreator(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new ParseHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new PreprocessHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); + this.AddService((provider, singletons) => AddSingleton(singletons, new WixBranding())); + + // Transients. + this.AddService((provider, singletons) => new CommandLineArguments(provider)); + this.AddService((provider, singletons) => new CommandLineContext(provider)); + this.AddService((provider, singletons) => new CommandLine.CommandLine(provider)); + this.AddService((provider, singletons) => new PreprocessContext(provider)); + this.AddService((provider, singletons) => new CompileContext(provider)); + this.AddService((provider, singletons) => new LibraryContext(provider)); + this.AddService((provider, singletons) => new LinkContext(provider)); + this.AddService((provider, singletons) => new ResolveContext(provider)); + this.AddService((provider, singletons) => new BindContext(provider)); + this.AddService((provider, singletons) => new DecompileContext(provider)); + this.AddService((provider, singletons) => new LayoutContext(provider)); + this.AddService((provider, singletons) => new InscribeContext(provider)); + this.AddService((provider, singletons) => new UnbindContext(provider)); + + this.AddService((provider, singletons) => new BindFileWithPath()); + this.AddService((provider, singletons) => new BindPath()); + this.AddService((provider, singletons) => new BindResult()); + this.AddService((provider, singletons) => new ComponentKeyPath()); + this.AddService((provider, singletons) => new DecompileResult()); + this.AddService((provider, singletons) => new IncludedFile()); + this.AddService((provider, singletons) => new PreprocessResult()); + this.AddService((provider, singletons) => new ResolvedDirectory()); + this.AddService((provider, singletons) => new ResolveFileResult()); + this.AddService((provider, singletons) => new ResolveResult()); + this.AddService((provider, singletons) => new ResolvedCabinet()); + this.AddService((provider, singletons) => new VariableResolution()); + + this.AddService((provider, singletons) => new Binder(provider)); + this.AddService((provider, singletons) => new Compiler(provider)); + this.AddService((provider, singletons) => new Decompiler(provider)); + this.AddService((provider, singletons) => new LayoutCreator(provider)); + this.AddService((provider, singletons) => new Preprocessor(provider)); + this.AddService((provider, singletons) => new Librarian(provider)); + this.AddService((provider, singletons) => new Linker(provider)); + this.AddService((provider, singletons) => new Resolver(provider)); + this.AddService((provider, singletons) => new Unbinder(provider)); + + this.AddService((provider, singletons) => new LocalizationParser(provider)); + this.AddService((provider, singletons) => new VariableResolver(provider)); + } + + private Dictionary, object>> CreationFunctions { get; } + + private Dictionary Singletons { get; } + + public object GetService(Type serviceType) + { + if (serviceType == null) + { + throw new ArgumentNullException(nameof(serviceType)); + } + + if (!this.Singletons.TryGetValue(serviceType, out var service)) + { + if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction)) + { + service = creationFunction(this, this.Singletons); + +#if DEBUG + if (!serviceType.IsAssignableFrom(service?.GetType())) + { + throw new InvalidOperationException($"Creation function for service type: {serviceType.Name} created incompatible service with type: {service?.GetType()}"); + } +#endif + } + } + + return service; + } + + public void AddService(Type serviceType, Func, object> creationFunction) + { + this.CreationFunctions[serviceType] = creationFunction; + } + + public void AddService(Func, T> creationFunction) where T : class + { + this.AddService(typeof(T), creationFunction); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } + } +} diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs b/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs new file mode 100644 index 00000000..8e07070b --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs @@ -0,0 +1,21 @@ +// 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.Extensibility.Services; + + /// + /// Class for creating . + /// + public static class WixToolsetServiceProviderFactory + { + /// + /// Creates a new . + /// + /// The created + public static IWixToolsetCoreServiceProvider CreateServiceProvider() + { + return new WixToolsetServiceProvider(); + } + } +} diff --git a/src/wix/appveyor.cmd b/src/wix/appveyor.cmd new file mode 100644 index 00000000..02db695b --- /dev/null +++ b/src/wix/appveyor.cmd @@ -0,0 +1,20 @@ +@setlocal +@pushd %~dp0 +@set _P=%~dp0build\Release\publish +@set _C=Release +@if /i "%1"=="debug" set _C=Debug + +:: Restore +msbuild -p:Configuration=%_C% -t:Restore || exit /b + +:: Build +msbuild -p:Configuration=%_C% || exit /b + +:: Test +dotnet test -c %_C% --no-build || exit /b + +:: Pack +msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack || exit /b + +@popd +@endlocal diff --git a/src/wix/appveyor.yml b/src/wix/appveyor.yml new file mode 100644 index 00000000..364569cf --- /dev/null +++ b/src/wix/appveyor.yml @@ -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. +# +# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml +# then update all of the repos. + +branches: + only: + - master + - develop + +image: Visual Studio 2019 + +version: 0.0.0.{build} +configuration: Release + +environment: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + NUGET_XMLDOC_MODE: skip + +build_script: + - appveyor.cmd + +test: off + +pull_requests: + do_not_increment_build_number: true + +nuget: + disable_publish_on_pr: true + +skip_branch_with_pr: true +skip_tags: true + +artifacts: +- path: build\Release\**\*.nupkg + name: nuget +- path: build\Release\**\*.snupkg + name: snupkg + +notifications: +- provider: Slack + incoming_webhook: + secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA= diff --git a/src/wix/nuget.config b/src/wix/nuget.config new file mode 100644 index 00000000..022f9240 --- /dev/null +++ b/src/wix/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj new file mode 100644 index 00000000..88210bd4 --- /dev/null +++ b/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj @@ -0,0 +1,32 @@ + + + + + + netcoreapp3.1 + false + Exe + + + + + + + + + $(BaseOutputPath)TestData\$(Configuration)\example.wixlib + + + + + + + + + + diff --git a/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs b/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs new file mode 100644 index 00000000..323b5e5e --- /dev/null +++ b/src/wix/test/CompileCoreTestExtensionWixlib/Program.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. + +using System.Collections.Generic; +using WixToolset.Core.TestPackage; + +namespace CompileCoreTestExtensionWixlib +{ + // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools. + // So we have this helper exe. + public class Program + { + public static void Main(string[] args) + { + var intermediateFolder = args[0]; + var wixlibPath = args[1]; + + var buildArgs = new List(); + buildArgs.Add("build"); + buildArgs.Add("-bindfiles"); + buildArgs.Add("-bindpath"); + buildArgs.Add("Data"); + buildArgs.Add("-intermediateFolder"); + buildArgs.Add(intermediateFolder); + buildArgs.Add("-o"); + buildArgs.Add(wixlibPath); + + foreach (var path in args[2].Split(';')) + { + buildArgs.Add(path); + } + + var result = WixRunner.Execute(buildArgs.ToArray()); + + result.AssertSuccess(); + } + } +} diff --git a/src/wix/test/Example.Extension/Data/example.txt b/src/wix/test/Example.Extension/Data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/Example.Extension/Data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/Example.Extension/Data/example.wxs b/src/wix/test/Example.Extension/Data/example.wxs new file mode 100644 index 00000000..af5d5086 --- /dev/null +++ b/src/wix/test/Example.Extension/Data/example.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/Example.Extension/Example.Extension.csproj b/src/wix/test/Example.Extension/Example.Extension.csproj new file mode 100644 index 00000000..9be10d35 --- /dev/null +++ b/src/wix/test/Example.Extension/Example.Extension.csproj @@ -0,0 +1,24 @@ + + + + + + netcoreapp3.1 + false + embedded + + + + + + + + + + + + + + + + diff --git a/src/wix/test/Example.Extension/ExampleCompilerExtension.cs b/src/wix/test/Example.Extension/ExampleCompilerExtension.cs new file mode 100644 index 00000000..5b8d4b3f --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleCompilerExtension.cs @@ -0,0 +1,195 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleCompilerExtension : BaseCompilerExtension + { + public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; + public string BundleExtensionId => "ExampleBundleExtension"; + + public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) + { + var processed = false; + + switch (parentElement.Name.LocalName) + { + case "Bundle": + case "Fragment": + switch (element.Name.LocalName) + { + case "ExampleEnsureTable": + this.ParseExampleEnsureTableElement(intermediate, section, element); + processed = true; + break; + case "ExampleSearch": + this.ParseExampleSearchElement(intermediate, section, element); + processed = true; + break; + case "ExampleSearchRef": + this.ParseExampleSearchRefElement(intermediate, section, element); + processed = true; + break; + } + break; + case "Component": + switch (element.Name.LocalName) + { + case "Example": + this.ParseExampleElement(intermediate, section, element); + processed = true; + break; + } + break; + } + + if (!processed) + { + base.ParseElement(intermediate, section, parentElement, element, context); + } + } + + private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string value = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + + case "Value": + value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + var symbol = this.ParseHelper.CreateSymbol(section, sourceLineNumbers, "Example", id); + symbol.Set(0, value); + } + } + + private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll); + } + + private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string searchFor = null; + string variable = null; + string condition = null; + string after = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SearchFor": + searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + this.ParseHelper.CreateWixSearchSymbol(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); + } + + if (!this.Messaging.EncounteredError) + { + var symbol = section.AddSymbol(new ExampleSearchSymbol(sourceLineNumbers, id) + { + SearchFor = searchFor, + }); + } + } + + private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, ExampleSymbolDefinitions.ExampleSearch, refId); + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleExtensionData.cs b/src/wix/test/Example.Extension/ExampleExtensionData.cs new file mode 100644 index 00000000..91d60eb9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleExtensionData.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 Example.Extension +{ + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleExtensionData : IExtensionData + { + public string DefaultCulture => null; + + public Intermediate GetLibrary(ISymbolDefinitionCreator symbolDefinitions) + { + return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", symbolDefinitions); + } + + public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) + { + symbolDefinition = ExampleSymbolDefinitions.ByName(name); + return symbolDefinition != null; + } + } +} \ No newline at end of file diff --git a/src/wix/test/Example.Extension/ExampleExtensionFactory.cs b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs new file mode 100644 index 00000000..e54561ee --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleExtensionFactory.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 Example.Extension +{ + using System; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + public class ExampleExtensionFactory : IExtensionFactory + { + private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; + + public ExampleExtensionFactory(IWixToolsetCoreServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + /// + /// This exists just to show it is possible to get a service provider to the extension factory. + /// + private IWixToolsetCoreServiceProvider ServiceProvider { get; } + + public bool TryCreateExtension(Type extensionType, out object extension) + { + if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) + { + if (this.preprocessorExtension == null) + { + this.preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); + } + + extension = this.preprocessorExtension; + } + else if (extensionType == typeof(ICompilerExtension)) + { + extension = new ExampleCompilerExtension(); + } + else if (extensionType == typeof(IExtensionData)) + { + extension = new ExampleExtensionData(); + } + else if (extensionType == typeof(IWindowsInstallerBackendBinderExtension)) + { + extension = new ExampleWindowsInstallerBackendExtension(); + } + else + { + extension = null; + } + + return extension != null; + } + } +} diff --git a/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs new file mode 100644 index 00000000..7244798a --- /dev/null +++ b/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.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 Example.Extension +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine + { + private string exampleValueFromCommandLine; + + public IReadOnlyCollection CommandLineSwitches => throw new NotImplementedException(); + + public ExamplePreprocessorExtensionAndCommandLine() + { + this.Prefixes = new[] { "ex" }; + } + + public void PreParse(ICommandLineContext context) + { + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) + { + this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument); + return true; + } + + return false; + } + + public bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) + { + command = null; + return false; + } + + public void PostParse() + { + } + + public override string GetVariableValue(string prefix, string name) + { + if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; + } + + return null; + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleRow.cs b/src/wix/test/Example.Extension/ExampleRow.cs new file mode 100644 index 00000000..fc20c6c9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleRow.cs @@ -0,0 +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 Example.Extension +{ + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + + public class ExampleRow : Row + { + public ExampleRow(SourceLineNumber sourceLineNumbers, Table table) + : base(sourceLineNumbers, table) + { + } + + public ExampleRow(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + : base(sourceLineNumbers, tableDefinition) + { + } + + public string Example + { + get { return (string)this.Fields[0].Data; } + set { this.Fields[0].Data = value; } + } + + public string Value + { + get { return (string)this.Fields[1].Data; } + set { this.Fields[1].Data = value; } + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleSearchSymbol.cs b/src/wix/test/Example.Extension/ExampleSearchSymbol.cs new file mode 100644 index 00000000..40a39292 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSearchSymbol.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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleSearchSymbolFields + { + SearchFor, + } + + public class ExampleSearchSymbol : IntermediateSymbol + { + public ExampleSearchSymbol() : base(ExampleSymbolDefinitions.ExampleSearch, null, null) + { + } + + public ExampleSearchSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.ExampleSearch, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; + + public string SearchFor + { + get => this.Fields[(int)ExampleSearchSymbolFields.SearchFor]?.AsString(); + set => this.Set((int)ExampleSearchSymbolFields.SearchFor, value); + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleSymbol.cs b/src/wix/test/Example.Extension/ExampleSymbol.cs new file mode 100644 index 00000000..314087e9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSymbol.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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleSymbolFields + { + Value, + } + + public class ExampleSymbol : IntermediateSymbol + { + public ExampleSymbol() : base(ExampleSymbolDefinitions.Example, null, null) + { + } + + public ExampleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.Example, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; + + public string Value + { + get => this.Fields[(int)ExampleSymbolFields.Value]?.AsString(); + set => this.Set((int)ExampleSymbolFields.Value, value); + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs b/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs new file mode 100644 index 00000000..f13d716d --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSymbolDefinitions.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 Example.Extension +{ + using System; + using WixToolset.Data; + using WixToolset.Data.Burn; + + public enum ExampleSymbolDefinitionType + { + Example, + ExampleSearch, + } + + public static class ExampleSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition Example = new IntermediateSymbolDefinition( + ExampleSymbolDefinitionType.Example.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(ExampleSymbolFields.Value), IntermediateFieldType.String), + }, + typeof(ExampleSymbol)); + + public static readonly IntermediateSymbolDefinition ExampleSearch = new IntermediateSymbolDefinition( + ExampleSymbolDefinitionType.ExampleSearch.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(ExampleSearchSymbolFields.SearchFor), IntermediateFieldType.String), + }, + typeof(ExampleSearchSymbol)); + + static ExampleSymbolDefinitions() + { + ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag); + } + + public static bool TryGetSymbolType(string name, out ExampleSymbolDefinitionType type) + { + return Enum.TryParse(name, out type); + } + + public static IntermediateSymbolDefinition ByName(string name) + { + if (!TryGetSymbolType(name, out var type)) + { + return null; + } + return ByType(type); + } + + public static IntermediateSymbolDefinition ByType(ExampleSymbolDefinitionType type) + { + switch (type) + { + case ExampleSymbolDefinitionType.Example: + return ExampleSymbolDefinitions.Example; + + case ExampleSymbolDefinitionType.ExampleSearch: + return ExampleSymbolDefinitions.ExampleSearch; + + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleTableDefinitions.cs b/src/wix/test/Example.Extension/ExampleTableDefinitions.cs new file mode 100644 index 00000000..a2b81698 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleTableDefinitions.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 Example.Extension +{ + using WixToolset.Data.WindowsInstaller; + + public static class ExampleTableDefinitions + { + public static readonly TableDefinition ExampleTable = new TableDefinition( + "Wix4Example", + ExampleSymbolDefinitions.Example, + new[] + { + new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), + new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), + }, + strongRowType: typeof(ExampleRow), + symbolIdIsPrimaryKey: true + ); + + public static readonly TableDefinition NotInAll = new TableDefinition( + "TableDefinitionNotExposedByExtension", + null, + new[] + { + new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), + new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), + }, + symbolIdIsPrimaryKey: true + ); + + public static readonly TableDefinition[] All = new[] { ExampleTable }; + } +} diff --git a/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs b/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs new file mode 100644 index 00000000..afccc56f --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.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 Example.Extension +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension + { + public override IReadOnlyCollection TableDefinitions => ExampleTableDefinitions.All; + + public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData output, TableDefinitionCollection tableDefinitions) + { + if (ExampleSymbolDefinitions.TryGetSymbolType(symbol.Definition.Name, out var symbolType)) + { + switch (symbolType) + { + case ExampleSymbolDefinitionType.Example: + { + var row = (ExampleRow)this.BackendHelper.CreateRow(section, symbol, output, ExampleTableDefinitions.ExampleTable); + row.Example = symbol.Id.Id; + row.Value = symbol[0].AsString(); + } + return true; + } + } + + return base.TryProcessSymbol(section, symbol, output, tableDefinitions); + } + } +} diff --git a/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs b/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs new file mode 100644 index 00000000..a83da7f6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.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 WixToolsetTest.Core.Burn +{ + using System; + using WixToolset.Core.Burn.Bundles; + using Xunit; + + public class BurnReaderFixture + { + [Fact] + public void CanReadUInt16Max() + { + var bytes = new byte[] { 0xFF, 0xFF }; + var offset = 0u; + + var result = BurnCommon.ReadUInt16(bytes, offset); + + Assert.Equal(UInt16.MaxValue, result); + } + + [Fact] + public void CanReadUInt32Max() + { + var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + var offset = 0u; + + var result = BurnCommon.ReadUInt32(bytes, offset); + + Assert.Equal(UInt32.MaxValue, result); + } + + [Fact] + public void CanReadUInt64Max() + { + var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + var offset = 0u; + + var result = BurnCommon.ReadUInt64(bytes, offset); + + Assert.Equal(UInt64.MaxValue, result); + } + } +} diff --git a/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj b/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj new file mode 100644 index 00000000..175ee1a9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj @@ -0,0 +1,28 @@ + + + + + + netcoreapp3.1 + false + embedded + + + + NU1701 + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs new file mode 100644 index 00000000..47b47ef5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ApprovedExeFixture + { + [Fact] + public void CanBuildWithApprovedExe() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildWithApprovedExe64() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs new file mode 100644 index 00000000..62ffe1eb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class BadInputFixture + { + [Fact] + public void SwitchIsNotConsideredAnArgument() + { + var result = WixRunner.Execute(new[] + { + "build", + "-bindpath", "-thisisaswitchnotanarg", + }); + + Assert.Single(result.Messages, m => m.Id == (int)ErrorMessages.Ids.ExpectedArgument); + // TODO: when CantBuildSingleExeBundleWithInvalidArgument is fixed, uncomment: + //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode); + } + + [Fact] + public void HandleInvalidIds() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "InvalidIds.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(330, result.ExitCode); + } + } + + [Fact] + public void CantBuildSingleExeBundleWithInvalidArgument() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + "-nonexistentswitch", "param", + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + + [Fact] + public void RegistryKeyWithoutAttributesDoesntCrash() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RegistryKey.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } + + [Fact] + public void BundleVariableWithBadTypeIsRejected() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleVariable.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(21, result.ExitCode); + } + } + + [Fact] + public void BundleVariableWithHiddenPersistedIsRejected() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(193, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs new file mode 100644 index 00000000..39e6b4aa --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs @@ -0,0 +1,96 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BindVariablesFixture + { + [Fact] + public void CanBuildBundleWithPackageBindVariables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildWithDefaultValue() + { + var folder = TestData.Get(@"TestData", "BindVariables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DefaultedVariable.wxs"), + "-bf", + "-intermediateFolder", intermediateFolder, + "-bindpath", folder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + } + } + + [Fact] + public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths() + { + var folder = TestData.Get(@"TestData", "WixlibWithBinaries"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + // Use names that aren't excluded in default .gitignores. + "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", + "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(103, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs new file mode 100644 index 00000000..9bdc9496 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs @@ -0,0 +1,46 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class BootstrapperApplicationFixture + { + [Fact] + public void CanSetBootstrapperApplicationDllDpiAwareness() + { + var folder = TestData.Get(@"TestData\BootstrapperApplication"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DpiAwareness.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var baDllSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(baDllSymbol); + + Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs new file mode 100644 index 00000000..b33b8891 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class BundleExtractionFixture + { + [Fact] + public void CanExtractBundleWithDetachedContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + var baFolderPath = Path.Combine(extractFolderPath, "UX"); + var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer"); + + // TODO: use WixRunner.Execute(string[]) to always go through the command line. + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }, serviceProvider, out var messages).Result; + + WixRunnerResult.AssertSuccess(result, messages); + Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning)); + + Assert.True(File.Exists(exePath)); + + var unbinder = serviceProvider.GetService(); + unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml"))); + Assert.False(Directory.Exists(attachedContainerFolderPath)); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs new file mode 100644 index 00000000..ab644080 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -0,0 +1,478 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.Burn; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.Resources; + using Xunit; + + public class BundleFixture + { + [Fact] + public void CanBuildMultiFileBundle() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSimpleBundle() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + using (var wixOutput = WixOutput.Read(pdbPath)) + { + + var intermediate = Intermediate.Load(wixOutput); + var section = intermediate.Sections.Single(); + + var bundleSymbol = section.Symbols.OfType().Single(); + Assert.Equal("1.0.0.0", bundleSymbol.Version); + + var previousVersion = bundleSymbol.Fields[(int)WixBundleSymbolFields.Version].PreviousValue; + Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); + + var msiSymbol = section.Symbols.OfType().Single(); + Assert.Equal("test.msi", msiSymbol.Id.Id); + + var extractResult = BundleExtractor.ExtractBAContainer(null, exePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var burnManifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var extractedBurnManifestData = File.ReadAllText(Path.Combine(baFolderPath, "manifest.xml"), Encoding.UTF8); + Assert.Equal(extractedBurnManifestData, burnManifestData); + + var baManifestData = wixOutput.GetData(BurnConstants.BootstrapperApplicationDataWixOutputStreamName); + var extractedBaManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BootstrapperApplicationData.xml"), Encoding.UTF8); + Assert.Equal(extractedBaManifestData, baManifestData); + + var bextManifestData = wixOutput.GetData(BurnConstants.BundleExtensionDataWixOutputStreamName); + var extractedBextManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BundleExtensionData.xml"), Encoding.UTF8); + Assert.Equal(extractedBextManifestData, bextManifestData); + + var logElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Log"); + var logElement = (XmlNode)Assert.Single(logElements); + Assert.Equal("", logElement.GetTestXml()); + + var registrationElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration"); + var registrationElement = (XmlNode)Assert.Single(registrationElements); + Assert.Equal($"" + + "" + + "", registrationElement.GetTestXml()); + + var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']"); + var msiPayload = (XmlNode)Assert.Single(msiPayloads); + Assert.Equal("", + msiPayload.GetTestXml(new Dictionary>() { { "Payload", new List { "FileSize", "Hash" } } })); + } + + var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); + manifestResource.Load(exePath); + var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); + Assert.Equal("" + + "" + + "" + + "~TestBundle" + + "" + + "" + + "" + + "true/pmPerMonitorV2, PerMonitor" + + "", actualManifestData); + } + } + + [Fact] + public void CanBuildX64Bundle() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] // TODO: go back to elevating warnings as errors. + { + "build", + "-arch", "x64", + Path.Combine(folder, "Bundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + var warning = Assert.Single(result.Messages.Where(m => m.Level == MessageLevel.Warning)); + Assert.Equal((int)WarningMessages.Ids.ExperimentalBundlePlatform, warning.Id); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); + manifestResource.Load(exePath); + var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); + Assert.Equal("" + + "" + + "" + + "~TestBundle" + + "" + + "" + + "" + + "true/pmPerMonitorV2, PerMonitor" + + "", actualManifestData); + } + } + + [Fact] + public void CanBuildSimpleBundleUsingExtensionBA() + { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSingleExeBundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildSingleExeRemotePayloadBundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var intermediate = Intermediate.Load(wixOutput); + var section = intermediate.Sections.Single(); + + var payloadSymbol = section.Symbols.OfType().Where(x => x.Id.Id == "NetFx462Web").Single(); + Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize); + } + } + } + + [Fact] + public void CantBuildWithDuplicateCacheIds() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal(8001, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithDuplicatePayloadNames() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'Auto2' has a duplicate Name 'burn.exe' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", + }, attachedContainerWarnings); + + var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'DuplicatePayloadNames.wxs' has a duplicate Name 'fakeba.dll' in the BA container. When extracting the container at runtime, the file will get overwritten.", + "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", + "The Payload 'uxYRbgitOs0K878jn5L_z7LdJ21KI' has a duplicate Name 'BundleExtensionData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", + }, baContainerErrors); + + var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", + "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", + }, externalErrors); + + var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.", + }, packageCacheErrors); + + Assert.Equal(14, result.Messages.Length); + } + } + + [Fact] + public void CantBuildWithOrphanPayload() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "OrphanPayload.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithPackageInMultipleContainers() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithUnscheduledPackage() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithUnscheduledRollbackBoundary() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs new file mode 100644 index 00000000..6d769bd6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -0,0 +1,365 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BundleManifestFixture + { + [Fact] + public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA"); + Assert.Equal(3, customElements.Count); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); + } + } + + [Fact] + public void PopulatesBAManifestWithPackageInformation() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties"); + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPackageProperties", new List { "DownloadSize", "PackageSize", "InstalledSize", "Version" } }, + }; + Assert.Equal(3, packageElements.Count); + Assert.Equal("", packageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", packageElements[1].GetTestXml()); + Assert.Equal("", packageElements[2].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void PopulatesBAManifestWithPayloadInformation() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties"); + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPayloadProperties", new List { "Size" } }, + }; + Assert.Equal(4, payloadElements.Count); + Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void PopulatesBEManifestWithBundleExtensionBundleCustomData() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE"); + Assert.Equal(3, customElements.Count); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); + } + } + + [Fact] + public void PopulatesManifestWithBundleExtension() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"), + Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); + Assert.Equal(1, bundleExtensionPayloads.Count); + var ignored = new Dictionary> + { + { "Payload", new List { "FileSize", "Hash", "SourcePath" } }, + }; + Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); + } + } + + [Fact] + public void PopulatesManifestWithBundleExtensionSearches() + { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), + Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); + Assert.Equal(2, extensionSearches.Count); + Assert.Equal("", extensionSearches[0].GetTestXml()); + Assert.Equal("", extensionSearches[1].GetTestXml()); + + var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); + Assert.Equal(1, bundleExtensionDatas.Count); + Assert.Equal("" + + "" + + "" + + "", bundleExtensionDatas[0].GetTestXml()); + + var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch"); + Assert.Equal(2, exampleSearches.Count); + } + } + + [Fact] + public void PopulatesManifestWithExePackages() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); + var ignoreAttributesByElementName = new Dictionary> + { + { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, + }; + Assert.Equal(2, exePackageElements.Count); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void PopulatesManifestWithSetVariables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SetVariable", "Simple.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable"); + Assert.Equal(6, setVariables.Count); + Assert.Equal("", setVariables[0].GetTestXml()); + Assert.Equal("", setVariables[1].GetTestXml()); + Assert.Equal("", setVariables[2].GetTestXml()); + Assert.Equal("", setVariables[3].GetTestXml()); + Assert.Equal("", setVariables[4].GetTestXml()); + Assert.Equal("", setVariables[5].GetTestXml()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs new file mode 100644 index 00000000..ad62dea6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs @@ -0,0 +1,107 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CabFixture + { + [Fact] + public void CabinetFilesSequencedCorrectly() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(cabPath)); + + var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); + var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); + + Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); + Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray()); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); + } + } + + [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")] + public void CabinetFilesSequencedCorrectlyUsingMergeModule() + { + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, ".data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(cabPath)); + + var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); + var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); + + Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray()); + Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray()); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); + } + } + + private class FileRow + { + public FileRow(string row) + { + row = row.Substring("File:".Length); + + var split = row.Split('\t'); + this.Id = split[0]; + this.Name = split[2]; + this.Sequence = Convert.ToInt32(split[7]); + } + + public string Id { get; set; } + + public string Name { get; set; } + + public int Sequence { get; set; } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs new file mode 100644 index 00000000..d24ba08c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class ComponentFixture + { + [Fact] + public void CanDetectDuplicateComponentGuids() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Component", "GuidCollision.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); + Array.Equals(new[] + { + 369, + 369 + }, errors.Select(e => e.Id).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs new file mode 100644 index 00000000..dd381dfe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.Burn; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ContainerFixture + { + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildWithCustomAttachedContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); + Assert.Equal(4, payloads.Count); + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); + } + } + + [Fact] + public void HarvestedPayloadsArePutInCorrectContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); + Assert.Equal(4, payloads.Count); + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); + } + } + + [Fact] + public void HarvestedPayloadsArePutInCorrectPackage() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> + { + { "MsiPackage", new List { "CacheId", "InstallSize", "Size", "ProductCode" } }, + { "Provides", new List { "Key" } }, + }; + var msiPackages = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "", + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "", + }, msiPackages); + } + } + + [Fact] + public void LayoutPayloadIsPutInContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + WixAssert.CompareLineByLine(new string[] + { + "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, payloads); + } + } + + [Fact] + public void MultipleAttachedContainersAreNotCurrentlySupported() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); + } + } + + [Fact] + public void PayloadIsNotPutInMultipleContainers() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, payloads); + } + } + + [Fact] + public void PopulatesBAManifestWithLayoutOnlyPayloads() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + WixAssert.CompareLineByLine(new string[] + { + "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPayloadProperties", new List { "Size" } }, + }; + var payloads = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + "", + "", + }, payloads); + } + } + + private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false) + { + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"), + }); + + result.AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs new file mode 100644 index 00000000..c6fa602b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs @@ -0,0 +1,48 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class CopyFileFixture + { + [Fact] + public void CanBuildCopyFile() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CopyFile", "CopyFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + var copyFileSymbol = section.Symbols.OfType().Single(); + Assert.Equal("MoveText", copyFileSymbol.Id.Id); + Assert.True(copyFileSymbol.Delete); + Assert.Equal("OtherFolder", copyFileSymbol.DestFolder); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs new file mode 100644 index 00000000..636b86a6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs @@ -0,0 +1,169 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CustomActionFixture + { + [Fact] + public void CanDetectCustomActionCycle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + Assert.Equal(176, result.ExitCode); + Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); + } + } + + [Fact] + public void CanDetectCustomActionCycleWithTail() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + Assert.Equal(176, result.ExitCode); + Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); + } + } + + [Fact] + public void PopulatesCustomActionTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { + "ActionText", + "AdminExecuteSequence", + "AdminUISequence", + "AdvtExecuteSequence", + "Binary", + "CustomAction", + "InstallExecuteSequence", + "InstallUISequence", + "Property", + }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); + Assert.Equal(new[] + { + "ActionText:CustomAction2\tProgess2Text\t", + "AdminExecuteSequence:CostFinalize\t\t1000", + "AdminExecuteSequence:CostInitialize\t\t800", + "AdminExecuteSequence:CustomAction2\t\t801", + "AdminExecuteSequence:FileCost\t\t900", + "AdminExecuteSequence:InstallAdminPackage\t\t3900", + "AdminExecuteSequence:InstallFiles\t\t4000", + "AdminExecuteSequence:InstallFinalize\t\t6600", + "AdminExecuteSequence:InstallInitialize\t\t1500", + "AdminExecuteSequence:InstallValidate\t\t1400", + "AdminUISequence:CostFinalize\t\t1000", + "AdminUISequence:CostInitialize\t\t800", + "AdminUISequence:CustomAction2\t\t801", + "AdminUISequence:ExecuteAction\t\t1300", + "AdminUISequence:FileCost\t\t900", + "AdvtExecuteSequence:CostFinalize\t\t1000", + "AdvtExecuteSequence:CostInitialize\t\t800", + "AdvtExecuteSequence:CustomAction2\t\t801", + "AdvtExecuteSequence:InstallFinalize\t\t6600", + "AdvtExecuteSequence:InstallInitialize\t\t1500", + "AdvtExecuteSequence:InstallValidate\t\t1400", + "AdvtExecuteSequence:PublishFeatures\t\t6300", + "AdvtExecuteSequence:PublishProduct\t\t6400", + "Binary:Binary1\t[Binary data]", + "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", + "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t", + "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", + "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", + "InstallExecuteSequence:CostFinalize\t\t1000", + "InstallExecuteSequence:CostInitialize\t\t800", + "InstallExecuteSequence:CreateFolders\t\t3700", + "InstallExecuteSequence:CustomAction2\t\t801", + "InstallExecuteSequence:FileCost\t\t900", + "InstallExecuteSequence:FindRelatedProducts\t\t25", + "InstallExecuteSequence:InstallFiles\t\t4000", + "InstallExecuteSequence:InstallFinalize\t\t6600", + "InstallExecuteSequence:InstallInitialize\t\t1500", + "InstallExecuteSequence:InstallValidate\t\t1400", + "InstallExecuteSequence:LaunchConditions\t\t100", + "InstallExecuteSequence:MigrateFeatureStates\t\t1200", + "InstallExecuteSequence:ProcessComponents\t\t1600", + "InstallExecuteSequence:PublishFeatures\t\t6300", + "InstallExecuteSequence:PublishProduct\t\t6400", + "InstallExecuteSequence:RegisterProduct\t\t6100", + "InstallExecuteSequence:RegisterUser\t\t6000", + "InstallExecuteSequence:RemoveExistingProducts\t\t1401", + "InstallExecuteSequence:RemoveFiles\t\t3500", + "InstallExecuteSequence:RemoveFolders\t\t3600", + "InstallExecuteSequence:UnpublishFeatures\t\t1800", + "InstallExecuteSequence:ValidateProductID\t\t700", + "InstallUISequence:CostFinalize\t\t1000", + "InstallUISequence:CostInitialize\t\t800", + "InstallUISequence:CustomAction2\t\t801", + "InstallUISequence:ExecuteAction\t\t1300", + "InstallUISequence:FileCost\t\t900", + "InstallUISequence:FindRelatedProducts\t\t25", + "InstallUISequence:LaunchConditions\t\t100", + "InstallUISequence:MigrateFeatureStates\t\t1200", + "InstallUISequence:ValidateProductID\t\t700", + "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs new file mode 100644 index 00000000..ee93b03a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -0,0 +1,234 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CustomTableFixture + { + [Fact] + public void PopulatesCustomTable1() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); + Assert.Equal(new[] + { + "CustomTable1:Row1\ttest.txt", + "CustomTable1:Row2\ttest.txt", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithLocalization() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); + Assert.Equal(new[] + { + "CustomTableLocalized:Row1\tThis is row one", + "CustomTableLocalized:Row2\tThis is row two", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePath() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePathSerialized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-lib", wixlibPath, + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void UnrealCustomTableIsNotPresentInMsi() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); + Assert.Empty(results); + } + } + + [Fact] + public void CanCompileAndDecompile() + { + var folder = TestData.Get(@"TestData"); + var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs"); + + var result = WixRunner.Execute(new[] + { + "build", + "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(msiPath)); + + result = WixRunner.Execute(new[] + { + "decompile", msiPath, + "-sw1060", + "-intermediateFolder", intermediateFolder, + "-o", decompiledWxsPath + }); + + result.AssertSuccess(); + + WixAssert.CompareXml(expectedFile, decompiledWxsPath); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs new file mode 100644 index 00000000..ab04da15 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -0,0 +1,86 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DecompileFixture + { + private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName) + { + var folder = TestData.Get(sourceFolder); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, msiName), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath); + } + } + + [Fact] + public void CanDecompileSingleFileCompressed() + { + DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs"); + } + + [Fact] + public void CanDecompile64BitSingleFileCompressed() + { + DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs"); + } + + [Fact] + public void CanDecompileNestedDirSearchUnderRegSearch() + { + DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs"); + } + + [Fact] + public void CanDecompileOldClassTableDefinition() + { + // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. + // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, + // and numerous foreign key relationships are missing. + DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs"); + } + + [Fact] + public void CanDecompileSequenceTables() + { + DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs"); + } + + [Fact] + public void CanDecompileShortcuts() + { + DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); + } + + [Fact] + public void CanDecompileNullComponent() + { + DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); + } + + [Fact] + public void CanDecompileMergeModuleWithTargetDirComponent() + { + DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs"); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs new file mode 100644 index 00000000..840b411e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs @@ -0,0 +1,180 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DependencyExtensionFixture + { + [Fact] + public void CanBuildBundleUsingExePackageWithProvides() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides") + .Cast() + .Select(e => e.GetTestXml()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, provides); + } + } + + [Fact] + public void CanBuildBundleUsingMsiWithProvides() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "UsingProvides", "Package.wxs"), + Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "UsingProvides"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "UsingProvides.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides") + .Cast() + .Select(e => e.GetTestXml()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + }, provides); + } + } + + [Fact] + public void CanBuildBundleWithCustomProviderKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "Registration", new List { "Id" } }, + }; + var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, registration); + } + } + + [Fact] + public void CanBuildPackageUsingProvides() + { + var folder = TestData.Get(@"TestData\UsingProvides"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "WixDependencyProvider"); + Assert.Equal(new[] + { + "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t", + }, results); + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs new file mode 100644 index 00000000..a61bdff3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -0,0 +1,271 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class DirectoryFixture + { + [Fact] + public void CanGet32bitProgramFiles6432Folder() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Directory", "Empty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFilesFolder:.", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } + + [Fact] + public void CanGet64bitProgramFiles6432Folder() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "Empty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFiles64Folder:.", + "ProgramFiles64Folder:TARGETDIR:PFiles64", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } + + [Fact] + public void CanGetDefaultName() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Directory", "DefaultName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + WixAssert.CompareLineByLine(new[] + { + "BinFolder\tCompanyFolder\t.", + "CompanyFolder\tProgramFilesFolder\tExample Corporation", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + WixAssert.CompareLineByLine(new[] + { + "BinFolder\tCompanyFolder\t.", + "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); + } + } + + [Fact] + public void CanGetDuplicateDir() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe", + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFiles64Folder:.", + "ProgramFiles64Folder:TARGETDIR:PFiles64", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } + + [Fact] + public void CanGetWithMultiNestedSubdirectory() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "Nested.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", + "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", + "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); + } + } + + [Fact] + public void CanGetDuplicateTargetSourceName() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "BinFolder\tProgramFilesFolder\tbin", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "BinFolder\tProgramFilesFolder\tbin", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs new file mode 100644 index 00000000..e2306dcd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs @@ -0,0 +1,52 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ExePackageFixture + { + [Fact] + public void ErrorWhenMissingDetectCondition() + { + var folder = TestData.Get(@"TestData", "ExePackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MissingDetectCondition.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(1153, result.ExitCode); + } + } + + [Fact] + public void ErrorWhenRequireDetectCondition() + { + var folder = TestData.Get(@"TestData", "ExePackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RequireDetectCondition.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(401, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs new file mode 100644 index 00000000..089658e6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs @@ -0,0 +1,153 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class ExtensionFixture + { + [Fact] + public void CanBuildAndQuery() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var build = new Builder(folder, typeof(ExampleExtensionFactory), new[] { Path.Combine(folder, "data") }); + + var results = build.BuildAndQuery(Build, "Wix4Example"); + Assert.Equal(new[] + { + "Wix4Example:Foo\tBar" + }, results); + } + + [Fact] + public void CanBuildWithExampleExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); + Assert.Equal("Foo", example.Id?.Id); + Assert.Equal("Bar", example[0].AsString()); + } + } + + [Fact] + public void CanParseCommandLineWithExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-example", "test", + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); + var section = intermediate.Sections.Single(); + + var property = section.Symbols.OfType().Where(p => p.Id.Id == "ExampleProperty").Single(); + Assert.Equal("ExampleProperty", property.Id.Id); + Assert.Equal("test", property.Value); + } + } + + [Fact] + public void CannotBuildWithMissingExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var exception = Assert.Throws(() => + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-ext", "ExampleExtension.DoesNotExist" + })); + + Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist' could not be found. Checked paths: ", exception.Message); + } + } + + [Fact] + public void CannotBuildWithMissingVersionedExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var exception = Assert.Throws(() => + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-ext", "ExampleExtension.DoesNotExist/1.0.0" + })); + + Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist/1.0.0' could not be found. Checked paths: ", exception.Message); + } + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs new file mode 100644 index 00000000..db9708a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs @@ -0,0 +1,174 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class LanguageFixture + { + [Fact] + public void CanBuildWithDefaultProductLanguage() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var directorySymbols = section.Symbols.OfType(); + WixAssert.CompareLineByLine(new[] + { + "INSTALLFOLDER:Example Corporation\\MsiPackage", + "ProgramFilesFolder:PFiles", + "TARGETDIR:SourceDir" + }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray()); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("0", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;0", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + WixAssert.CompareLineByLine(new[] + { + "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", + "INSTALLFOLDER:oekcr5lq|MsiPackage", + "ProgramFilesFolder:PFiles", + "TARGETDIR:SourceDir" + }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); + } + } + + [Fact] + public void CanBuildEnuWxl() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1033", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1033", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildJpnWxl() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.ja-jp.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1041", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1041", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("932", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildJpnWxlWithEnuSummaryInfo() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1041", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1041", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs new file mode 100644 index 00000000..cfe4d3f1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs @@ -0,0 +1,174 @@ + +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class LinkerFixture + { + [Fact] + public void MustCompileBeforeLinking() + { + var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null); + var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null); + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + + var listener = new TestMessageListener(); + var messaging = serviceProvider.GetService(); + messaging.SetListener(listener); + + var creator = serviceProvider.GetService(); + var context = serviceProvider.GetService(); + context.Extensions = Array.Empty(); + context.ExtensionData = Array.Empty(); + context.Intermediates = new[] { intermediate1, intermediate2 }; + context.SymbolDefinitionCreator = creator; + + var linker = serviceProvider.GetService(); + linker.Link(context); + + Assert.Equal((int)ErrorMessages.Ids.IntermediatesMustBeCompiled, messaging.LastErrorNumber); + Assert.Single(listener.Messages); + Assert.EndsWith("TestIntermediate1, TestIntermediate2", listener.Messages[0].ToString()); + } + + [Fact] + public void CanBuildWithOverridableActions() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1008", // this is expected for this test + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var actions = section.Symbols.OfType().Where(wat => wat.Action.StartsWith("Set")).ToList(); + Assert.Equal(2, actions.Count); + //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileSymbolFields.Source].AsPath().Path); + //Assert.Equal(@"test.txt", wixFile[WixFileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void MissingEntrySectionDetectedProduct() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + try + { + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + } + catch (WixException we) + { + Assert.Equal("Could not find entry section in provided list of intermediates. Expected section of type 'Product'.", we.Message); + return; + } + + Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); + } + } + + [Fact] + public void MissingEntrySectionDetectedWixipl() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + try + { + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.wixipl") + }); + } + catch (WixException we) + { + Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); + return; + } + + Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); + } + } + + [Fact] + public void MissingEntrySectionDetectedUnknown() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + try + { + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.bob") + }); + } + catch (WixException we) + { + Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); + return; + } + + Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs new file mode 100644 index 00000000..de18e30c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class MediaFixture + { + [Fact] + public void CanBuildMultiMedia() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Media", "MultiMedia.wxs"), + "-bindpath", Path.Combine(folder, "Media", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var mediaSymbols = section.Symbols.OfType().OrderBy(m => m.DiskId).ToList(); + var fileSymbols = section.Symbols.OfType().OrderBy(f => f.Sequence).ToList(); + Assert.Equal(1, mediaSymbols[0].DiskId); + Assert.Equal(2, mediaSymbols[0].LastSequence); + Assert.Equal(2, mediaSymbols[1].DiskId); + Assert.Equal(4, mediaSymbols[1].LastSequence); + Assert.Equal(new[] + { + "a1.txt", + "a2.txt", + "b1.txt", + "b2.txt", + }, fileSymbols.Select(f => f.Name).ToArray()); + Assert.Equal(new[] + { + 1, + 2, + 3, + 4, + }, fileSymbols.Select(f => f.Sequence).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs new file mode 100644 index 00000000..17e91692 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs @@ -0,0 +1,113 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class ModuleFixture + { + [Fact] + public void CanBuildSimpleModule() + { + var folder = TestData.Get(@"TestData\SimpleModule"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Module.wxs"), + "-loc", Path.Combine(folder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msm") + }); + + result.AssertSuccess(); + + var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); + Assert.True(File.Exists(msmPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); + WixAssert.CompareLineByLine(new[] + { + "MergeRedirectFolder\tTARGETDIR\t.", + "NotTheMergeRedirectFolder\tTARGETDIR\t.", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var fileSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); + WixAssert.CompareLineByLine(new[] + { + $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", + $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", + }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var fileRows = data.Tables["File"].Rows; + Assert.Equal(new[] + { + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); + + var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); + Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(new[] + { + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); + } + } + + [Fact] + public void CanSuppressModularization() + { + var folder = TestData.Get(@"TestData\SuppressModularization"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Module.wxs"), + "-loc", Path.Combine(folder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-sw1079", + "-sw1086", + "-o", Path.Combine(intermediateFolder, @"bin\test.msm") + }); + + result.AssertSuccess(); + + var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); + + var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" }); + WixAssert.CompareLineByLine(new[] + { + "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t", + "Property:MsiHiddenProperties\tTest" + }, rows); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs new file mode 100644 index 00000000..3bdfa0ef --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -0,0 +1,838 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class MsiFixture + { + [Fact] + public void CanBuildSingleFile() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Compiled)); + Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Resolved)); + Assert.True(intermediate.HasLevel(WixToolset.Data.WindowsInstaller.IntermediateLevels.FullyBound)); + + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildSingleFileCompressed() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplate() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel=low", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildMultipleFilesCompressed() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1079", // TODO: why does this test need to create a second cab which is empty? + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanFailBuildMissingFile() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "does-not-exist"), + "-bindpath", Path.Combine(folder, "also-does-not-exist"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }, out var messages); + Assert.Equal(103, result); + + var error = messages.Single(m => m.Level == MessageLevel.Error); + var errorMessage = error.ToString(); + var checkedPaths = errorMessage.Substring(errorMessage.IndexOf(':') + 1).Split(new[] { ',' }).Select(s => s.Trim()).ToArray(); + Assert.Equal(new[] + { + "test.txt", + Path.Combine(folder, "does-not-exist", "test.txt"), + Path.Combine(folder, "also-does-not-exist", "test.txt"), + }, checkedPaths); + } + } + + [Fact] + public void CanBuildWithErrorTable() + { + var folder = TestData.Get(@"TestData\ErrorsInUI"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var errors = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + Assert.Equal("Category 55 Emergency Doomsday Crisis", errors["1234"].Message.Trim()); + Assert.Equal(" ", errors["5678"].Message); + + var customAction1 = section.Symbols.OfType().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single(); + Assert.Equal("1234", customAction1.Target); + + var customAction2 = section.Symbols.OfType().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single(); + Assert.Equal("If you see this, something went wrong.", customAction2.Target); + } + } + + [Fact] + public void CanLoadPdbGeneratedByBuild() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); + Assert.True(File.Exists(pdbPath)); + + var output = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: true); + Assert.NotNull(output); + } + } + + [Fact] + public void CanLoadPdbGeneratedByBuildViaWixOutput() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); + Assert.True(File.Exists(pdbPath)); + + var wixOutput = WixOutput.Read(pdbPath); + var output = WindowsInstallerData.Load(wixOutput, suppressVersionCheck: true); + Assert.NotNull(output); + } + } + + [Fact] + public void CanBuildManualUpgrade() + { + var folder = TestData.Get(@"TestData\ManualUpgrade"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }, out var messages); + + Assert.Equal(0, result); + + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(pdbPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(pdbPath); + var section = intermediate.Sections.Single(); + + var upgradeSymbol = section.Symbols.OfType().Single(); + Assert.False(upgradeSymbol.ExcludeLanguages); + Assert.True(upgradeSymbol.IgnoreRemoveFailures); + Assert.False(upgradeSymbol.VersionMaxInclusive); + Assert.True(upgradeSymbol.VersionMinInclusive); + Assert.Equal("13.0.0", upgradeSymbol.VersionMax); + Assert.Equal("12.0.0", upgradeSymbol.VersionMin); + Assert.False(upgradeSymbol.OnlyDetect); + Assert.Equal("BLAHBLAHBLAH", upgradeSymbol.ActionProperty); + + var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false); + var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single(); + Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1)); + } + } + + [Fact] + public void CanBuildWixipl() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.wixipl") + }, out var messages); + + Assert.Equal(0, result); + + var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); + + Assert.Equal(new[]{ + "test.wixipl" + }, builtFiles.Select(Path.GetFileName).ToArray()); + } + } + + [Fact] + public void CanBuildWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.wixlib") + }, out var messages); + + Assert.Equal(0, result); + + var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); + + Assert.Equal(new[]{ + "test.wixlib" + }, builtFiles.Select(Path.GetFileName).ToArray()); + } + } + + [Fact] + public void CanBuildBinaryWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-bindfiles", + "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); + + result.AssertSuccess(); + + using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) + { + Assert.NotNull(wixout.GetDataStream("wix-ir.json")); + + var text = wixout.GetData("wix-ir/test.txt"); + Assert.Equal("This is test.txt.", text); + } + } + } + + [Fact] + public void CanBuildBinaryWixlibWithCollidingFilenames() + { + var folder = TestData.Get(@"TestData\SameFileFolders"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "TestComponents.wxs"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-bindfiles", + "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); + + result.AssertSuccess(); + + using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) + { + Assert.NotNull(wixout.GetDataStream("wix-ir.json")); + + var text = wixout.GetData("wix-ir/test.txt"); + Assert.Equal(@"This is a\test.txt.", text); + + var text2 = wixout.GetData("wix-ir/test.txt-1"); + Assert.Equal(@"This is b\test.txt.", text2); + + var text3 = wixout.GetData("wix-ir/test.txt-2"); + Assert.Equal(@"This is c\test.txt.", text3); + } + } + } + + [Fact] + public void CanBuildWithIncludePath() + { + var folder = TestData.Get(@"TestData\IncludePath"); + var bindpath = Path.Combine(folder, "data"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", bindpath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + "-i", bindpath); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildWithAssembly() + { + var folder = TestData.Get(@"TestData\Assembly"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\candle.exe"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"candle.exe", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var msiAssemblyNameSymbols = section.Symbols.OfType(); + Assert.Equal(new[] + { + "culture", + "fileVersion", + "name", + "processorArchitecture", + "publicKeyToken", + "version" + }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Name).ToArray()); + + Assert.Equal(new[] + { + "neutral", + "3.11.11810.0", + "candle", + "x86", + "256B3414DFA97718", + "3.0.0.0" + }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Value).ToArray()); + } + } + + [Fact] + public void CanBuild64bit() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var platformSummary = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("x64;1033", platformSummary.Value); + } + } + + [Fact] + public void CanBuildSharedComponent() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + // Only one component is shared. + var sharedComponentSymbols = section.Symbols.OfType(); + Assert.Equal(1, sharedComponentSymbols.Sum(t => t.Shared ? 1 : 0)); + + // And it is this one. + var sharedComponentSymbol = sharedComponentSymbols.Single(t => t.Id.Id == "Shared.dll"); + Assert.True(sharedComponentSymbol.Shared); + } + } + + [Fact] + public void CanBuildSetProperty() + { + var folder = TestData.Get(@"TestData\SetProperty"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var output = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); + var caRows = output.Tables["CustomAction"].Rows.Single(); + Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); + Assert.Equal("51", caRows.FieldAsString(1)); + Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); + Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3)); + } + } + + [Fact] + public void CanBuildVersionIndependentProgId() + { + var folder = TestData.Get(@"TestData\ProgId"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var progids = section.Symbols.OfType().OrderBy(symbol => symbol.ProgId).ToList(); + Assert.Equal(new[] + { + "Foo.File.hol", + "Foo.File.hol.15" + }, progids.Select(p => p.ProgId).ToArray()); + + Assert.Equal(new[] + { + "Foo.File.hol.15", + null + }, progids.Select(p => p.ParentProgIdRef).ToArray()); + } + } + + [Fact] + public void CanBuildInstanceTransform() + { + var folder = TestData.Get(@"TestData\InstanceTransform"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); + var substorage = output.SubStorages.Single(); + Assert.Equal("I1", substorage.Name); + + var data = substorage.Data; + Assert.Equal(new[] + { + "_SummaryInformation", + "Property", + "Upgrade" + }, data.Tables.Select(t => t.Name).ToArray()); + + Assert.Equal(new[] + { + "INSTANCEPROPERTY\tI1", + "ProductName\tMsiPackage (Instance 1)", + }, JoinRows(data.Tables["Property"])); + + Assert.Equal(new[] + { + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0", + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0" + }, JoinRows(data.Tables["Upgrade"])); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void FailsBuildAtLinkTimeForMissingEnsureTable() + { + var folder = TestData.Get(@"TestData"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + Assert.Collection(result.Messages, + first => + { + Assert.Equal(MessageLevel.Error, first.Level); + Assert.Equal("The identifier 'WixCustomTable:TableDefinitionNotExposedByExtension' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.", first.ToString()); + }); + + Assert.False(File.Exists(msiPath)); + } + } + + private static string[] JoinRows(Table table) + { + return table.Rows.Select(r => JoinFields(r.Fields)).ToArray(); + + string JoinFields(Field[] fields) + { + return String.Join('\t', fields.Select(f => f.ToString())); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs new file mode 100644 index 00000000..71edddc6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -0,0 +1,1040 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class MsiQueryFixture + { + [Fact] + public void PopulatesAppIdTableWhenAdvertised() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppId", "Advertised.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); + WixAssert.CompareLineByLine(new[] + { + "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromComponentSearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", + "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromDirectorySearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch", + "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromFileSearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "FileSearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", + "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", + "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromRegistrySearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", + "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromRegistrySearch64() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", + "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18", + }, results); + } + } + + [Fact] + public void PopulatesClassTablesWhenIconIndexIsZero() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Class", "IconIndex0.wxs"), + Path.Combine(folder, "Icon", "SampleIcon.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Class" }); + WixAssert.CompareLineByLine(new[] + { + "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", + }, results); + } + } + + [Fact] + public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", + "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", + "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp", + "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp", + "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp", + }, results); + } + } + + [Fact] + public void PopulatesControlTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + + var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" }); + WixAssert.CompareLineByLine(new[] + { + "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", + "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t", + "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t", + "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun", + "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t", + "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled", + "ControlCondition:FirstDialog\tHeader\tHide\tInstalled", + "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed", + "InstallUISequence:CostFinalize\t\t1000", + "InstallUISequence:CostInitialize\t\t800", + "InstallUISequence:ExecuteAction\t\t1300", + "InstallUISequence:FileCost\t\t900", + "InstallUISequence:FindRelatedProducts\t\t25", + "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298", + "InstallUISequence:LaunchConditions\t\t100", + "InstallUISequence:MigrateFeatureStates\t\t1200", + "InstallUISequence:SecondDialog\tNOT Installed\t1299", + "InstallUISequence:ValidateProductID\t\t700", + }, results); + } + } + + [Fact] + public void PopulatesCreateFolderTableForNullKeypathComponents() + { + var folder = TestData.Get(@"TestData\Components"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); + WixAssert.CompareLineByLine(new[] + { + "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", + }, results); + } + } + + [Fact] + public void PopulatesDirectoryTableWithValidDefaultDir() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1031", // this is expected for this test + Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); + WixAssert.CompareLineByLine(new[] + { + "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", + "Directory:Folder1\tINSTALLFOLDER\tFolder.1", + "Directory:Folder12\tINSTALLFOLDER\tFolder.12", + "Directory:Folder123\tINSTALLFOLDER\tFolder.123", + "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234", + "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", + "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", + "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", + "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", + "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.", + "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles", + "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", + "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", + "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", + "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly", + "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL", + "Directory:TARGETDIR\t\tSourceDir", + }, results); + } + } + + [Fact] + public void PopulatesEnvironmentTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Environment", "Environment.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); + WixAssert.CompareLineByLine(new[] + { + "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", + "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest", + }, results); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesExampleTableBecauseOfEnsureTable() + { + var folder = TestData.Get(@"TestData"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" }); + Assert.Empty(results["Wix4Example"]); + } + } + + [Fact] + public void PopulatesFeatureTableWithParent() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); + WixAssert.CompareLineByLine(new[] + { + "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", + "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", + "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0", + }, results); + } + } + + [Fact] + public void PopulatesFontTableFromFontTitle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Font", "FontTitle.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Font" }); + WixAssert.CompareLineByLine(new[] + { + "Font:test.txt\tFakeFont", + }, results); + } + } + + [Fact] + public void PopulatesFontTableFromTrueType() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Font", "TrueType.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Font" }); + WixAssert.CompareLineByLine(new[] + { + "Font:TrueTypeFontFile\t", + }, results); + } + } + + [Fact] + public void PopulatesInstallExecuteSequenceTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" }); + WixAssert.CompareLineByLine(new[] + { + "InstallExecuteSequence:CostFinalize\t\t1000", + "InstallExecuteSequence:CostInitialize\t\t800", + "InstallExecuteSequence:CreateFolders\t\t3700", + "InstallExecuteSequence:FileCost\t\t900", + "InstallExecuteSequence:FindRelatedProducts\t\t25", + "InstallExecuteSequence:InstallFiles\t\t4000", + "InstallExecuteSequence:InstallFinalize\t\t6600", + "InstallExecuteSequence:InstallInitialize\t\t1500", + "InstallExecuteSequence:InstallValidate\t\t1400", + "InstallExecuteSequence:LaunchConditions\t\t100", + "InstallExecuteSequence:MigrateFeatureStates\t\t1200", + "InstallExecuteSequence:ProcessComponents\t\t1600", + "InstallExecuteSequence:PublishFeatures\t\t6300", + "InstallExecuteSequence:PublishProduct\t\t6400", + "InstallExecuteSequence:RegisterProduct\t\t6100", + "InstallExecuteSequence:RegisterUser\t\t6000", + "InstallExecuteSequence:RemoveExistingProducts\t\t1401", + "InstallExecuteSequence:RemoveFiles\t\t3500", + "InstallExecuteSequence:RemoveFolders\t\t3600", + "InstallExecuteSequence:UnpublishFeatures\t\t1800", + "InstallExecuteSequence:ValidateProductID\t\t700", + }, results); + } + } + + [Fact] + public void PopulatesLockPermissionsTableWithEmptyPermissions() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); + WixAssert.CompareLineByLine(new[] + { + "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", + }, results); + } + } + + [Fact] + public void PopulatesMsiAssemblyTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Assembly", "Win32Assembly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "Assembly", "data"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); + WixAssert.CompareLineByLine(new[] + { + "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", + "MsiAssemblyName:test.txt\tname\tMyApplication.app", + "MsiAssemblyName:test.txt\tversion\t1.0.0.0", + }, results); + } + } + + [Fact] + public void PopulatesReserveCostTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); + WixAssert.CompareLineByLine(new[] + { + "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", + }, results); + } + } + + [Fact] + public void PopulatesServiceTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); + WixAssert.CompareLineByLine(new[] + { + "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", + "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", + }, results); + } + } + + [Fact] + public void PopulatesTextStyleTableWhenColorIsNull() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TextStyle", "ColorNull.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); + WixAssert.CompareLineByLine(new[] + { + "TextStyle:FirstTextStyle\tArial\t2\t\t", + }, results); + } + } + + [Fact] + public void PopulatesTextStyleTableWhenSizeIsLocalized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); + WixAssert.CompareLineByLine(new[] + { + "TextStyle:CustomFont\tTahoma\t8\t\t", + }, results); + } + } + + [Fact] + public void PopulatesTypeLibTableWhenLanguageIsZero() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TypeLib", "Language0.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); + WixAssert.CompareLineByLine(new[] + { + "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", + }, results); + } + } + + [Fact] + public void PopulatesUpgradeTableFromManualUpgrade() + { + var folder = TestData.Get(@"TestData\ManualUpgrade"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }, out var messages); + + Assert.Equal(0, result); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); + WixAssert.CompareLineByLine(new[] + { + "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", + }, results); + } + } + + [Fact] + public void PopulatesUpgradeTableFromDetectOnlyUpgrade() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); + WixAssert.CompareLineByLine(new[] + { + "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", + "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", + "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", + }, results); + + var prefix = "Property:SecureCustomProperties\t"; + var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); + WixAssert.CompareLineByLine(new[] + { + "RELPRODFOUND", + "WIX_DOWNGRADE_DETECTED", + "WIX_UPGRADE_DETECTED", + }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray()); + } + } + + [Fact] + public void CanMergeModule() + { + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, ".data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + Assert.Empty(section.Symbols.OfType()); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + Assert.Empty(data.Tables["File"].Rows); + + var results = Query.QueryDatabase(msiPath, new[] { "File" }); + WixAssert.CompareLineByLine(new[] + { + "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" + }, results); + + var files = Query.GetCabinetFiles(cabPath); + WixAssert.CompareLineByLine(new[] + { + "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + }, files.Select(f => f.Name).ToArray()); + } + } + + [Fact] + public void CanPublishComponentWithMultipleFeatureComponents() + { + var folder = TestData.Get(@"TestData\PublishComponent"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" }); + WixAssert.CompareLineByLine(new[] + { + "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2", + "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs new file mode 100644 index 00000000..a566b490 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs @@ -0,0 +1,131 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsiTransactionFixture + { + [Fact] + public void CantBuildX64AfterX86Bundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var exePath = Path.Combine(binFolder, "test.exe"); + + BuildMsiPackages(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1151", // this is expected for this test + Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal(390, result.ExitCode); + } + } + + [Fact] + public void CanBuildX86AfterX64Bundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var exePath = Path.Combine(binFolder, "test.exe"); + + BuildMsiPackages(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1151", // this is expected for this test + Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder) + { + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"), + }); + + result.AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs new file mode 100644 index 00000000..475afcf0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsuPackageFixture + { + [Fact] + public void CanBuildBundleWithMsuPackage() + { + var folder = TestData.Get(@"TestData", "MsuPackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, "bin", "test.exe") + }); + + result.AssertSuccess(); + Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe"))); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs new file mode 100644 index 00000000..6b2d8bfa --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs @@ -0,0 +1,211 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class PackagePayloadFixture + { + [Fact] + public void CanSpecifyPackagePayloadInPayloadGroup() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); + var ignoreAttributesByElementName = new Dictionary> + { + { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, + }; + Assert.Equal(1, exePackageElements.Count); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + + var payloadElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='burn.exe']"); + Assert.Equal(1, payloadElements.Count); + Assert.Equal("", payloadElements[0].GetTestXml()); + } + } + + [Fact] + public void ErrorWhenMissingSourceFileAndHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "MissingSourceFileAndHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(44, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenMissingSourceFileAndName() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "MissingSourceFileAndName.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(44, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(4, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MspPackagePayload element contains an unexpected attribute 'Hash'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedHashAndMissingDownloadUrl() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(10, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedSourceFileAndHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(35, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenWrongPackagePayloadInPayloadGroup() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + Assert.Equal(407, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The ExePackagePayload element can only be used for ExePackages.", + "The location of the package related to previous error.", + "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs new file mode 100644 index 00000000..cdba85de --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.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 WixToolsetTest.CoreIntegration +{ + using System.Linq; + using WixToolset.Core; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class ParseFixture + { + [Fact] + public void GeneratesCorrectCustomActionIdentifiers() + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var section = new IntermediateSection("section", SectionType.Fragment); + var parseHelper = serviceProvider.GetService(); + + parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86); + parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86); + parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); + parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86); + parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64); + + var simpleReferences = section.Symbols.OfType(); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction32_X86").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_X86").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_A64").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X86").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X64").FirstOrDefault()); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs new file mode 100644 index 00000000..483e3fd5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -0,0 +1,279 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using System.Xml; + using System.Xml.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Burn; + using Xunit; + + public class PatchFixture + { + private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; + + [Fact] + public void CanBuildSimplePatch() + { + var folder = TestData.Get(@"TestData\PatchSingle"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + + var doc = GetExtractPatchXml(patchPath); + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + + var names = Query.GetSubStorageNames(patchPath); + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(tempFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + Assert.True(File.Exists(cab)); + + var files = Query.GetCabinetFiles(cab); + Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); + } + } + + [Fact] + public void CanBuildSimplePatchWithNoFileChanges() + { + var folder = TestData.Get(@"TestData\PatchNoFileChanges"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + + var doc = GetExtractPatchXml(patchPath); + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + + var names = Query.GetSubStorageNames(patchPath); + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(tempFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + Assert.True(File.Exists(cab)); + + var files = Query.GetCabinetFiles(cab); + Assert.Empty(files); + } + } + + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")] + public void CanBuildPatchFromProductWithFilesFromWixlib() + { + var folder = TestData.Get(@"TestData\PatchFromWixlib"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolderBaseline = fs.GetFolder(); + var tempFolderUpdate = fs.GetFolder(); + var tempFolderPatch = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + } + } + + [Fact] + public void CanBuildBundleWithNonSpecificPatches() + { + var folder = TestData.Get(@"TestData\PatchNonSpecific"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B"); + var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B"); + var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true); + var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true); + var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true); + var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); + var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder); + var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder); + + VerifyPatchTargetCodes(bundleAPdb, new[] + { + "", + }); + VerifyPatchTargetCodes(bundleBPdb, new[] + { + "", + "", + }); + VerifyPatchTargetCodes(bundleCPdb, new string[0]); + } + } + + [Fact] + public void CanBuildBundleWithSlipstreamPatch() + { + var folder = TestData.Get(@"TestData\PatchSingle"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); + var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); + + using (var wixOutput = WixOutput.Read(bundleAPdb)) + { + var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var doc = new XmlDocument(); + doc.LoadXml(manifestData); + var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); + var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr); + Assert.Equal(1, slipstreamMspNodes.Count); + Assert.Equal("", slipstreamMspNodes[0].GetTestXml()); + } + } + } + + private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) + { + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var doc = new XmlDocument(); + doc.LoadXml(manifestData); + var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); + var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr); + + var actual = new List(); + foreach (XmlNode patchTargetCodeNode in patchTargetCodes) + { + actual.Add(patchTargetCodeNode.GetTestXml()); + } + + WixAssert.CompareLineByLine(expected, actual.ToArray()); + } + } + + private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) + { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Package.wxs"), + "-d", "V=" + defineV, + "-d", "A=" + defineA, + "-d", "B=" + defineB, + "-bindpath", Path.Combine(sourceFolder, ".data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath, + "-ext", extensionPath, + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, bool hasNoFiles = false) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var args = new List + { + "build", + hasNoFiles ? "-sw1079" : " ", + Path.Combine(sourceFolder, @"Patch.wxs"), + "-d", "V=" + defineV, + "-bindpath", Path.Combine(baseFolder, "bin"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }; + + foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty()) + { + args.Add("-bindpath"); + args.Add(additionaBindPath); + } + + var result = WixRunner.Execute(args.ToArray()); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static string BuildBundle(string outputName, string sourceFolder, string baseFolder) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Bundle.wxs"), + Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"), + "-bindpath", Path.Combine(baseFolder, "bin"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static XDocument GetExtractPatchXml(string path) + { + var buffer = new StringBuilder(65535); + var size = buffer.Capacity; + + var er = MsiExtractPatchXMLData(path, 0, buffer, ref size); + if (er != 0) + { + throw new Win32Exception(er); + } + + return XDocument.Parse(buffer.ToString()); + } + + [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData); + + [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs new file mode 100644 index 00000000..23f6a9ba --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -0,0 +1,212 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class PayloadFixture + { + [Fact] + public void CanParseValidName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ValidName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + Assert.Empty(result.Messages); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var payloadSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(payloadSymbol); + + var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]); + } + } + + [Fact] + public void CanCanonicalizeName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var payloadSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(payloadSymbol); + + var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); + } + } + + [Fact] + public void RejectsAbsoluteName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AbsoluteName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.InRange(result.ExitCode, 2, int.MaxValue); + + var expectedIllegalRelativeLongFileName = 1; + var expectedPayloadMustBeRelativeToCache = 2; + Assert.Equal(expectedIllegalRelativeLongFileName, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count()); + Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); + } + } + + [Fact] + public void RejectsPayloadSharedBetweenPackageAndBA() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode); + } + } + + [Fact] + public void ReplacesDownloadUrlPlaceholders() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), + Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "Container", new List { "FileSize", "Hash" } }, + { "Payload", new List { "FileSize", "Hash" } }, + }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + "", + @"", + @"", + }, payloads); + + var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, containers); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs new file mode 100644 index 00000000..ae8a1bcc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -0,0 +1,181 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class PreprocessorFixture + { + [Fact] + public void PreprocessDirectly() + { + var folder = TestData.Get(@"TestData\IncludePath"); + var sourcePath = Path.Combine(folder, "Package.wxs"); + var includeFolder = Path.Combine(folder, "data"); + var includeFile = Path.Combine(includeFolder, "Package.wxi"); + + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + + var context = serviceProvider.GetService(); + context.SourcePath = sourcePath; + context.IncludeSearchPaths = new[] { includeFolder }; + + var preprocessor = serviceProvider.GetService(); + var result = preprocessor.Preprocess(context); + + var includedFile = result.IncludedFiles.Single(); + Assert.NotNull(result.Document); + Assert.Equal(includeFile, includedFile.Path); + Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName); + Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value); + Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName); + Assert.Null(includedFile.SourceLineNumbers.Parent); + } + + [Fact] + public void IncludeSourceLineNumbersPreserved() + { + var folder = TestData.Get(@"TestData\IncludePath"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-includepath", Path.Combine(folder, "data"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var intermediate = Intermediate.Load(output); + var component = intermediate.Sections.Single().Symbols.OfType().Single(); + Assert.Equal(3, component.SourceLineNumbers.LineNumber); + Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber); + + var encoded = component.SourceLineNumbers.GetEncoded(); + var decoded = SourceLineNumber.CreateFromEncoded(encoded); + Assert.Equal(3, decoded.LineNumber); + Assert.Equal(5, decoded.Parent.LineNumber); + } + } + } + + [Fact] + /// + /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" + /// which is only defined on 64-bit Windows. + /// + public void SupportParensInEnvironmentVariables() + { + var folder = TestData.Get(@"TestData", "Preprocessor"); + + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var context = serviceProvider.GetService(); + context.SourcePath = Path.Combine(folder, "EnvParens.wxs"); + + var preprocessor = serviceProvider.GetService(); + var result = preprocessor.Preprocess(context); + Assert.NotNull(result.Document); + } + + [Fact] + public void VariableRedefinitionIsAWarning() + { + var folder = TestData.Get(@"TestData\Variables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision); + Assert.Single(warning); + } + } + + [Fact] + public void ForEachLoopsWork() + { + var folder = TestData.Get(@"TestData\ForEach"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + } + } + + [Fact] + public void NonterminatedPreprocessorInstructionShowsSourceLineNumber() + { + var folder = TestData.Get(@"TestData\BadIf"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + Assert.Equal(147, result.ExitCode); + Assert.StartsWith("Found a ", result.Messages.Single().ToString()); + } + } + } +} + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs new file mode 100644 index 00000000..e4d95b5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs @@ -0,0 +1,173 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class RegistryFixture + { + [Fact] + public void PopulatesRegistryTableFromRegistryValue() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValue.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", + "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", + }, results); + } + } + + [Fact] + public void PopulatesRegistryTableFromRegistryValueMultiString() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", + "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", + }, results); + } + } + + [Fact] + public void DuplicateRegistryValueIdsAreDetectedSmoothly() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }, out var messages); + + Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count()); + Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count()); + } + } + + [Fact] + public void PopulatesRegistryTableFromRemoveRegistryKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", + }, results); + } + } + + [Fact] + public void PopulatesRegistryTableWithoutExtraBackslash() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent", + "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs new file mode 100644 index 00000000..9e19abb0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs @@ -0,0 +1,41 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class RollbackBoundaryFixture + { + [Fact] + public void CanStartChainWithRollbackBoundary() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs new file mode 100644 index 00000000..3b6c50c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ShortcutFixture + { + [Fact] + public void CanBuildShortcutNameWithShortname() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + + [Fact] + public void PopulatesMsiShortcutPropertyTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", + "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs new file mode 100644 index 00000000..15276b18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs @@ -0,0 +1,100 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class SoftwareTagFixture + { + private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; + private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; + + [Fact] + public void CanBuildPackageWithTag() + { + var folder = TestData.Get(@"TestData\ProductTag"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); + + var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; + var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); + results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); + WixAssert.CompareLineByLine(new[] + { + "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", + "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", + "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" + }, results.ToArray()); + } + + [Fact] + public void CanBuildBundleWithTag() + { + var testDataFolder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), + Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), + "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), + "-bindpath", Path.Combine(testDataFolder, "ProductTag"), + "-intermediateFolder", Path.Combine(intermediateFolder, "package"), + "-o", Path.Combine(baseFolder, "package", @"test.msi") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), + "-bindpath", Path.Combine(testDataFolder, "BundleTag"), + "-bindpath", Path.Combine(baseFolder, "package"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + + using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var badata = ouput.GetDataStream("wix-burndata.xml"); + var doc = XDocument.Load(badata); + + var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; + + var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); + File.WriteAllText(swidTagPath, swidTag); + + var docTag = XDocument.Load(swidTagPath); + var title = docTag.Root.Attribute("name").Value; + var version = docTag.Root.Attribute("version").Value; + Assert.Equal("~TagTestBundle", title); + Assert.Equal("4.3.2.1", version); + } + } + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe new file mode 100644 index 00000000..2a4f423f Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs new file mode 100644 index 00000000..b34c547d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs new file mode 100644 index 00000000..4dd701f0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs new file mode 100644 index 00000000..6b9fe013 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs new file mode 100644 index 00000000..e255c83d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs new file mode 100644 index 00000000..c17d9848 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi new file mode 100644 index 00000000..ea1296c3 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs new file mode 100644 index 00000000..f800264d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs new file mode 100644 index 00000000..8be5abb2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs new file mode 100644 index 00000000..c345305d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs new file mode 100644 index 00000000..e0c84c63 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs new file mode 100644 index 00000000..45cc7114 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe new file mode 100644 index 00000000..18129b73 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest new file mode 100644 index 00000000..0da1f6d0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs new file mode 100644 index 00000000..3caa20ff --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs new file mode 100644 index 00000000..1d7ebb94 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs new file mode 100644 index 00000000..a2d49b18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs new file mode 100644 index 00000000..0c350042 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs new file mode 100644 index 00000000..4fe7e097 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs new file mode 100644 index 00000000..5ebe5472 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs @@ -0,0 +1,6 @@ + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs new file mode 100644 index 00000000..78f3ebd3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs new file mode 100644 index 00000000..92a9602f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs new file mode 100644 index 00000000..a00874ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs new file mode 100644 index 00000000..c717680b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs new file mode 100644 index 00000000..fc53c4a2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs new file mode 100644 index 00000000..6cf8528e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs new file mode 100644 index 00000000..c3528a67 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt new file mode 100644 index 00000000..3b862323 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt @@ -0,0 +1 @@ +This is test.txt diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs new file mode 100644 index 00000000..5b41e807 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs new file mode 100644 index 00000000..7f5ea456 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs new file mode 100644 index 00000000..e52302d4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs new file mode 100644 index 00000000..eefae822 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs new file mode 100644 index 00000000..fd8d3698 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs new file mode 100644 index 00000000..c5a93eb3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs new file mode 100644 index 00000000..7303a05a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs new file mode 100644 index 00000000..f44fb7bc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll new file mode 100644 index 00000000..64061ea0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll @@ -0,0 +1 @@ +This is fakeba.dll. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs new file mode 100644 index 00000000..78e754c1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs new file mode 100644 index 00000000..18cdfd32 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs new file mode 100644 index 00000000..a93b23ef --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs new file mode 100644 index 00000000..e738b407 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs new file mode 100644 index 00000000..b0bde4f6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs new file mode 100644 index 00000000..514f9243 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs new file mode 100644 index 00000000..c0dc9bc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi new file mode 100644 index 00000000..2cd10f09 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs new file mode 100644 index 00000000..15a9a0ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs new file mode 100644 index 00000000..db07af2c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt new file mode 100644 index 00000000..8c874ae7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt @@ -0,0 +1 @@ +This is other.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs new file mode 100644 index 00000000..a0e921cb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs new file mode 100644 index 00000000..d7b5bdc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs new file mode 100644 index 00000000..beaf70bf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs new file mode 100644 index 00000000..ec757c5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs new file mode 100644 index 00000000..e175a18f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs new file mode 100644 index 00000000..0c5f8c7e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs new file mode 100644 index 00000000..28900e55 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs new file mode 100644 index 00000000..c7f549a3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs new file mode 100644 index 00000000..90d66cc3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs new file mode 100644 index 00000000..be991c65 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs new file mode 100644 index 00000000..c64ef143 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs new file mode 100644 index 00000000..ff8741cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs new file mode 100644 index 00000000..f8ce1c38 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs new file mode 100644 index 00000000..10c4f91f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs new file mode 100644 index 00000000..d7d86008 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs new file mode 100644 index 00000000..d32e808c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs new file mode 100644 index 00000000..08a9c470 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl new file mode 100644 index 00000000..bc2ccf04 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl @@ -0,0 +1,7 @@ + + + + This is row one + This is row two + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs new file mode 100644 index 00000000..e1da74f8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt new file mode 100644 index 00000000..97f701ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt @@ -0,0 +1 @@ +This is file1.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt new file mode 100644 index 00000000..46493186 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt @@ -0,0 +1 @@ +This is file2.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs new file mode 100644 index 00000000..71553e2a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi new file mode 100644 index 00000000..81335041 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs new file mode 100644 index 00000000..246bcafc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi new file mode 100644 index 00000000..9cb6d6bc Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs new file mode 100644 index 00000000..81915759 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi new file mode 100644 index 00000000..762b136c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs new file mode 100644 index 00000000..7c5fe3cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm new file mode 100644 index 00000000..2a7b5e3a Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs new file mode 100644 index 00000000..2f277956 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs new file mode 100644 index 00000000..6df8a7c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs new file mode 100644 index 00000000..4d188d3a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs new file mode 100644 index 00000000..9c3a9690 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs new file mode 100644 index 00000000..ec6e62df --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs new file mode 100644 index 00000000..3e7887c4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs new file mode 100644 index 00000000..6e9a4495 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs new file mode 100644 index 00000000..50cf6850 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs new file mode 100644 index 00000000..cc87b49f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs new file mode 100644 index 00000000..a58b68c8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs new file mode 100644 index 00000000..01767abb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs new file mode 100644 index 00000000..de9744a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl new file mode 100644 index 00000000..066e16bb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl @@ -0,0 +1,9 @@ + + + + + A newer version of [ProductName] is already installed. + MsiPackage + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs new file mode 100644 index 00000000..287085e8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs new file mode 100644 index 00000000..88a4ac81 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs new file mode 100644 index 00000000..5c84f33e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs new file mode 100644 index 00000000..e57180f7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs new file mode 100644 index 00000000..0b094860 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs new file mode 100644 index 00000000..be302720 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs new file mode 100644 index 00000000..6fb9ef05 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs new file mode 100644 index 00000000..6ac48963 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs new file mode 100644 index 00000000..8fff563e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs new file mode 100644 index 00000000..1de84e81 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs new file mode 100644 index 00000000..0bd80c50 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs new file mode 100644 index 00000000..7a0485ed --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi new file mode 100644 index 00000000..03885e3e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi new file mode 100644 index 00000000..f2df3b86 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi @@ -0,0 +1,4 @@ + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs new file mode 100644 index 00000000..7826d673 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl new file mode 100644 index 00000000..f7453566 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl new file mode 100644 index 00000000..ef287da7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl new file mode 100644 index 00000000..10ebf2c5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs new file mode 100644 index 00000000..13c79e90 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl new file mode 100644 index 00000000..596ee077 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs new file mode 100644 index 00000000..dfae2157 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs new file mode 100644 index 00000000..4fd3493a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs new file mode 100644 index 00000000..e7492db4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt new file mode 100644 index 00000000..ad9cdcb5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt @@ -0,0 +1 @@ +This is a1.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt new file mode 100644 index 00000000..d5de23de --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt @@ -0,0 +1 @@ +This is a2.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt new file mode 100644 index 00000000..88bc4a56 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt @@ -0,0 +1 @@ +This is b1.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt new file mode 100644 index 00000000..38525276 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt @@ -0,0 +1 @@ +This is b2.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs new file mode 100644 index 00000000..e6527a36 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs new file mode 100644 index 00000000..f1c939db --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs new file mode 100644 index 00000000..dbca3393 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll new file mode 100644 index 00000000..b3cf17d8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll @@ -0,0 +1 @@ +This is a fake BA DLL diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu new file mode 100644 index 00000000..d63da4be --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu @@ -0,0 +1 @@ +This is a fake MSU package diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs new file mode 100644 index 00000000..2b1a1a0f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..82797ebe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs new file mode 100644 index 00000000..0bf0e963 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs new file mode 100644 index 00000000..5e1b99ff --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs new file mode 100644 index 00000000..f220d81a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..149870a4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs new file mode 100644 index 00000000..3c361c49 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs new file mode 100644 index 00000000..8e62f660 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs new file mode 100644 index 00000000..f79da874 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..dda306cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt @@ -0,0 +1 @@ +This ia A v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt @@ -0,0 +1 @@ +This is B v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt @@ -0,0 +1 @@ +This ia B v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs new file mode 100644 index 00000000..c9dcdd72 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs new file mode 100644 index 00000000..d39170c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs new file mode 100644 index 00000000..5cb8ede8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs new file mode 100644 index 00000000..dab959d5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs new file mode 100644 index 00000000..889b1220 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs new file mode 100644 index 00000000..4a8f5630 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs new file mode 100644 index 00000000..7fb3cb56 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs new file mode 100644 index 00000000..201d177b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs new file mode 100644 index 00000000..62a89af3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs new file mode 100644 index 00000000..1b01774c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs new file mode 100644 index 00000000..f0630ead --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs new file mode 100644 index 00000000..f9d2a55a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt @@ -0,0 +1 @@ +This ia A v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt @@ -0,0 +1 @@ +This is B v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt @@ -0,0 +1 @@ +This ia B v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs new file mode 100644 index 00000000..bc460636 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs new file mode 100644 index 00000000..e3845382 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs new file mode 100644 index 00000000..dc94d688 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs new file mode 100644 index 00000000..544b80ec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs new file mode 100644 index 00000000..f8f38ea6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs new file mode 100644 index 00000000..5263cbd4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs new file mode 100644 index 00000000..9c37a27d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs new file mode 100644 index 00000000..68d115c5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs @@ -0,0 +1,4 @@ + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs new file mode 100644 index 00000000..37a2c462 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs new file mode 100644 index 00000000..5bf78a9d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs new file mode 100644 index 00000000..f62bbd0e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs new file mode 100644 index 00000000..433be7f0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs new file mode 100644 index 00000000..0621eb8d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs new file mode 100644 index 00000000..d3b31db5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs new file mode 100644 index 00000000..5166be16 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs new file mode 100644 index 00000000..8f4f661d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs new file mode 100644 index 00000000..452aea69 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs new file mode 100644 index 00000000..1fb2e906 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs new file mode 100644 index 00000000..fe6e179e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs new file mode 100644 index 00000000..c62c571d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs new file mode 100644 index 00000000..a55a1e18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs new file mode 100644 index 00000000..3218295b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs new file mode 100644 index 00000000..ecfccfcb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs new file mode 100644 index 00000000..bbad63e6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt new file mode 100644 index 00000000..1970cae6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt @@ -0,0 +1 @@ +This is a\test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt new file mode 100644 index 00000000..fa2c7082 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt @@ -0,0 +1 @@ +This is b\test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt new file mode 100644 index 00000000..1c0cbda6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt @@ -0,0 +1 @@ +This is c\test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs new file mode 100644 index 00000000..d5379e7b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi new file mode 100644 index 00000000..7f894091 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs new file mode 100644 index 00000000..65cba20e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs new file mode 100644 index 00000000..d3f8accf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs new file mode 100644 index 00000000..7e8f2e99 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs new file mode 100644 index 00000000..f16fce0d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs new file mode 100644 index 00000000..da1e4f38 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs new file mode 100644 index 00000000..27f2ab9b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs new file mode 100644 index 00000000..d704bbf1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi new file mode 100644 index 00000000..8737f3c2 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl new file mode 100644 index 00000000..bc1dee83 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + ~TestBundle + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs new file mode 100644 index 00000000..21749c07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs new file mode 100644 index 00000000..f5fe9885 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs new file mode 100644 index 00000000..48f53ae3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll new file mode 100644 index 00000000..0e461ba8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll @@ -0,0 +1 @@ +This is Shared.dll. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt new file mode 100644 index 00000000..8b986220 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt @@ -0,0 +1 @@ +This is test.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll new file mode 100644 index 00000000..970efdf0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll @@ -0,0 +1 @@ +This is a fakeba.dll \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi new file mode 100644 index 00000000..0722d60e Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm new file mode 100644 index 00000000..6f179aba Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs new file mode 100644 index 00000000..3c999812 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + Example Company + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj new file mode 100644 index 00000000..597d4318 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj @@ -0,0 +1,48 @@ + + + + Debug + x86 + 0.9 + 27df04c6-3cef-4b9a-bac6-4e78d188384f + MergeModule1 + Module + MergeModule1 + MergeModule1 + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + + + + + + + + FgwepExtension.wixext + $(WixExtDir)\FgwepExtension.wixext.dll + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs new file mode 100644 index 00000000..8317e7af --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs new file mode 100644 index 00000000..cad1f049 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs new file mode 100644 index 00000000..0d459f02 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs new file mode 100644 index 00000000..d7b5bdc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs new file mode 100644 index 00000000..b8e9f59c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs new file mode 100644 index 00000000..baa0c6b1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + Example Company + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs new file mode 100644 index 00000000..f4ce9c48 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs new file mode 100644 index 00000000..669de6ec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl new file mode 100644 index 00000000..77d46861 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl @@ -0,0 +1,13 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + Tahoma + 8 + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs new file mode 100644 index 00000000..a591fdd9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs new file mode 100644 index 00000000..fa64f98f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs new file mode 100644 index 00000000..587d8e95 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs new file mode 100644 index 00000000..59839f30 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs new file mode 100644 index 00000000..7e459e9a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs new file mode 100644 index 00000000..7de55810 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -0,0 +1,31 @@ + + + + + + + += 4 AND $(sys.WIXMAJORVERSION) < 5 ?> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs new file mode 100644 index 00000000..f8203a07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs new file mode 100644 index 00000000..df867923 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt new file mode 100644 index 00000000..eab3a9b5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt @@ -0,0 +1 @@ +This is test2.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs new file mode 100644 index 00000000..7e6eee9f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs new file mode 100644 index 00000000..b29a785f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs new file mode 100644 index 00000000..7d1a4ae1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll new file mode 100644 index 00000000..fd36c768 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll @@ -0,0 +1 @@ +This is alpha\foo.dll. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll new file mode 100644 index 00000000..292925c7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll @@ -0,0 +1 @@ +This is mips\foo.dll. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll new file mode 100644 index 00000000..663e9d99 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll @@ -0,0 +1 @@ +This is powerpc\foo.dll. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs new file mode 100644 index 00000000..5330305e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using WixToolset.Core.TestPackage; + using Xunit; + + public class TestXmlFixture + { + [Fact] + public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() + { + var original = @" + + + + +"; + var expected = ""; + var ignored = new Dictionary> { { "Target", new List { "One", "Two", "Missing" } } }; + Assert.Equal(expected, original.GetTestXml(ignored)); + } + + [Fact] + public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesAllNamespacesToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() + { + var original = @" + + + + +"; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesXmlDeclarationToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs new file mode 100644 index 00000000..15e5d334 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs @@ -0,0 +1,75 @@ + +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using WixToolset.Core; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility.Services; + using Xunit; + + public class VariableResolverFixture + { + [Fact] + public void CanRecursivelyResolveVariables() + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var variableResolver = serviceProvider.GetService(); + + var variables = new Dictionary() + { + { "ProductName", new BindVariable() { Id = "ProductName", Value = "Localized Product Name" } }, + { "ProductNameEdition", new BindVariable() { Id = "ProductNameEdition", Value = "!(loc.ProductName) Enterprise Edition" } }, + { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, + }; + + var localization = new Localization(0, null, "x-none", variables, new Dictionary()); + + variableResolver.AddLocalization(localization); + + var result = variableResolver.ResolveVariables(null, "These are not the loc strings you're looking for."); + Assert.Equal("These are not the loc strings you're looking for.", result.Value); + Assert.False(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductName)"); + Assert.Equal("Welcome to Localized Product Name", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEdition)"); + Assert.Equal("Welcome to Localized Product Name Enterprise Edition", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion)"); + Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(bind.property.ProductVersion)"); + Assert.Equal("Welcome to !(bind.property.ProductVersion)", result.Value); + Assert.False(result.UpdatedValue); + Assert.True(result.DelayedResolve); + + var withUnknownLocString = "Welcome to !(loc.UnknownLocalizationVariable)"; + Assert.Throws(() => variableResolver.ResolveVariables(null, withUnknownLocString)); + + result = variableResolver.ResolveVariables(null, withUnknownLocString, errorOnUnknown: false); + Assert.Equal(withUnknownLocString, result.Value); + Assert.False(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable)"); + Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable)", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); + Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); + Assert.True(result.UpdatedValue); + Assert.True(result.DelayedResolve); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion) !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); + Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3 !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); + Assert.True(result.UpdatedValue); + Assert.True(result.DelayedResolve); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs new file mode 100644 index 00000000..c5b6c261 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs @@ -0,0 +1,63 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class WarningFixture + { + [Fact] + public void SuppressedWarningsWithWarningAsErrorsAreNotErrors() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(warningsAsErrors: true, new[] + { + "build", + "-sw1152", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + } + } + + [Fact] + public void WarningsAsErrorsTreatsWarningsAsErrors() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(warningsAsErrors: true, new[] + { + "build", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal((int)WarningMessages.Ids.PathCanonicalized, result.ExitCode); + + var message = Assert.Single(result.Messages); + Assert.Equal(MessageLevel.Warning, message.Level); // TODO: is this right? + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj new file mode 100644 index 00000000..fc62e932 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -0,0 +1,32 @@ + + + + + + netcoreapp3.1 + false + embedded + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs new file mode 100644 index 00000000..942f253f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs @@ -0,0 +1,205 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Example.Extension; + using Xunit; + + public class WixiplFixture + { + [Fact] + public void CanBuildSingleFile() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixiplPath = Path.Combine(intermediateFolder, @"test.wixipl"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixiplPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixiplPath); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(intermediateFolder, @"test.wixipl"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CannotBuildWithSourceFileAndWixipl() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixipl") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(intermediateFolder, @"test.wixipl"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode); + } + } + + [Fact] + public void CanBuildMsiUsingExtensionLibrary() + { + var folder = TestData.Get(@"TestData\Wixipl"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-ext", extensionPath, + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + { + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + + { + var binary = section.Symbols.OfType().Single(); + var path = binary[BinarySymbolFields.Data].AsPath().Path; + Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path); + Assert.EndsWith(@"wix-ir\example.txt", path); + Assert.Equal(@"BinFromWir", binary.Id.Id); + } + } + } + + [Fact] + public void CanBuildWixiplUsingExtensionLibrary() + { + var folder = TestData.Get(@"TestData\Wixipl"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-ext", extensionPath, + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixipl"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(intermediateFolder, @"test.wixipl"), + "-ext", extensionPath, + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + { + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + + { + var binary = section.Symbols.OfType().Single(); + var path = binary[BinarySymbolFields.Data].AsPath().Path; + Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path); + Assert.EndsWith(@"wix-ir\example.txt", path); + Assert.Equal(@"BinFromWir", binary.Id.Id); + } + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs new file mode 100644 index 00000000..d7296cfe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -0,0 +1,316 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class WixlibFixture + { + [Fact] + public void CanBuildSimpleBundleUsingWixlib() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildWixlibWithBinariesFromNamedBindPaths() + { + var folder = TestData.Get(@"TestData\WixlibWithBinaries"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + // Use names that aren't excluded in default .gitignores. + "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}", + "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + var binarySymbols = wixlib.Sections.SelectMany(s => s.Symbols).OfType().ToList(); + Assert.Equal(3, binarySymbols.Count); + Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll")); + Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-1")); + Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-2")); + } + } + + [Fact] + public void CanBuildSingleFileUsingWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + + Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); + Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var wixFile = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanOverridePathWixVariable() + { + var folder = TestData.Get(@"TestData\WixVariableOverride"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + + Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); + Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var wixFile = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path); + } + } + + [Fact] + public void CanBuildWithExtensionUsingWixlib() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); + Assert.Equal("Foo", example.Id?.Id); + Assert.Equal("Bar", example[0].AsString()); + } + } + + [Fact] + public void CanBuildWithExtensionUsingMultipleWixlibs() + { + var folder = TestData.Get(@"TestData\ComplexExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"components.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "OtherComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"other.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"components.wixlib"), + "-lib", Path.Combine(intermediateFolder, @"other.wixlib"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbols = section.Symbols.OfType().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbols[0][FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", fileSymbols[0][FileSymbolFields.Source].PreviousValue.AsPath().Path); + Assert.Equal(Path.Combine(folder, @"data\other.txt"), fileSymbols[1][FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"other.txt", fileSymbols[1][FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray(); + Assert.Equal(new string[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray()); + Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[0].AsString()).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs new file mode 100644 index 00000000..57351b27 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class WixlibQueryFixture + { + [Fact] + public void UpgradeProducesReferenceToRemoveExistingProducts() + { + var folder = TestData.Get(@"TestData\Upgrade"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DetectOnly.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var wixSimpleRefSymbols = allSymbols.OfType(); + var repRef = wixSimpleRefSymbols.Where(t => t.Table == "WixAction" && + t.PrimaryKeys == "InstallExecuteSequence/RemoveExistingProducts") + .SingleOrDefault(); + Assert.NotNull(repRef); + } + } + + [Fact] + public void TypeLibLanguageAsStringReturnsZero() + { + var folder = TestData.Get(@"TestData\TypeLib"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Language0.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var typeLibSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(typeLibSymbol); + + var fields = typeLibSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal("0", fields[1]); + } + } + } +} diff --git a/version.json b/version.json deleted file mode 100644 index 5f857771..00000000 --- a/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "4.0", - "publicReleaseRefSpec": [ - "^refs/heads/master$" - ], - "cloudBuild": { - "buildNumber": { - "enabled": true - } - } -} -- cgit v1.2.3-55-g6feb